zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

program.go (9141B)


      1 // Copyright 2014 Hajime Hoshi
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package opengl
     16 
     17 import (
     18 	"fmt"
     19 	"runtime"
     20 
     21 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     22 	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
     23 	"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
     24 )
     25 
     26 const floatSizeInBytes = 4
     27 
     28 // arrayBufferLayoutPart is a part of an array buffer layout.
     29 type arrayBufferLayoutPart struct {
     30 	// TODO: This struct should belong to a program and know it.
     31 	name string
     32 	num  int
     33 }
     34 
     35 // arrayBufferLayout is an array buffer layout.
     36 //
     37 // An array buffer in OpenGL is a buffer representing vertices and
     38 // is passed to a vertex shader.
     39 type arrayBufferLayout struct {
     40 	parts []arrayBufferLayoutPart
     41 	total int
     42 }
     43 
     44 func (a *arrayBufferLayout) names() []string {
     45 	ns := make([]string, len(a.parts))
     46 	for i, p := range a.parts {
     47 		ns[i] = p.name
     48 	}
     49 	return ns
     50 }
     51 
     52 // totalBytes returns the size in bytes for one element of the array buffer.
     53 func (a *arrayBufferLayout) totalBytes() int {
     54 	if a.total != 0 {
     55 		return a.total
     56 	}
     57 	t := 0
     58 	for _, p := range a.parts {
     59 		t += floatSizeInBytes * p.num
     60 	}
     61 	a.total = t
     62 	return a.total
     63 }
     64 
     65 // newArrayBuffer creates OpenGL's buffer object for the array buffer.
     66 func (a *arrayBufferLayout) newArrayBuffer(context *context) buffer {
     67 	return context.newArrayBuffer(a.totalBytes() * graphics.IndicesNum)
     68 }
     69 
     70 // enable starts using the array buffer.
     71 func (a *arrayBufferLayout) enable(context *context) {
     72 	for i := range a.parts {
     73 		context.enableVertexAttribArray(i)
     74 	}
     75 	total := a.totalBytes()
     76 	offset := 0
     77 	for i, p := range a.parts {
     78 		context.vertexAttribPointer(i, p.num, total, offset)
     79 		offset += floatSizeInBytes * p.num
     80 	}
     81 }
     82 
     83 // disable stops using the array buffer.
     84 func (a *arrayBufferLayout) disable(context *context) {
     85 	// TODO: Disabling should be done in reversed order?
     86 	for i := range a.parts {
     87 		context.disableVertexAttribArray(i)
     88 	}
     89 }
     90 
     91 // theArrayBufferLayout is the array buffer layout for Ebiten.
     92 var theArrayBufferLayout = arrayBufferLayout{
     93 	// Note that GL_MAX_VERTEX_ATTRIBS is at least 16.
     94 	parts: []arrayBufferLayoutPart{
     95 		{
     96 			name: "A0",
     97 			num:  2,
     98 		},
     99 		{
    100 			name: "A1",
    101 			num:  2,
    102 		},
    103 		{
    104 			name: "A2",
    105 			num:  4,
    106 		},
    107 	},
    108 }
    109 
    110 func init() {
    111 	vertexFloatNum := theArrayBufferLayout.totalBytes() / floatSizeInBytes
    112 	if graphics.VertexFloatNum != vertexFloatNum {
    113 		panic(fmt.Sprintf("vertex float num must be %d but %d", graphics.VertexFloatNum, vertexFloatNum))
    114 	}
    115 }
    116 
    117 type programKey struct {
    118 	useColorM bool
    119 	filter    driver.Filter
    120 	address   driver.Address
    121 }
    122 
    123 // openGLState is a state for
    124 type openGLState struct {
    125 	// arrayBuffer is OpenGL's array buffer (vertices data).
    126 	arrayBuffer buffer
    127 
    128 	// elementArrayBuffer is OpenGL's element array buffer (indices data).
    129 	elementArrayBuffer buffer
    130 
    131 	// programs is OpenGL's program for rendering a texture.
    132 	programs map[programKey]program
    133 
    134 	lastProgram       program
    135 	lastUniforms      map[string]interface{}
    136 	lastActiveTexture int
    137 }
    138 
    139 var (
    140 	zeroBuffer  buffer
    141 	zeroProgram program
    142 )
    143 
    144 // reset resets or initializes the OpenGL state.
    145 func (s *openGLState) reset(context *context) error {
    146 	if err := context.reset(); err != nil {
    147 		return err
    148 	}
    149 
    150 	s.lastProgram = zeroProgram
    151 	context.useProgram(zeroProgram)
    152 	s.lastUniforms = map[string]interface{}{}
    153 
    154 	// When context lost happens, deleting programs or buffers is not necessary.
    155 	// However, it is not assumed that reset is called only when context lost happens.
    156 	// Let's delete them explicitly.
    157 	if s.programs == nil {
    158 		s.programs = map[programKey]program{}
    159 	} else {
    160 		for k, p := range s.programs {
    161 			context.deleteProgram(p)
    162 			delete(s.programs, k)
    163 		}
    164 	}
    165 
    166 	// On browsers (at least Chrome), buffers are already detached from the context
    167 	// and must not be deleted by DeleteBuffer.
    168 	if runtime.GOOS != "js" {
    169 		if !s.arrayBuffer.equal(zeroBuffer) {
    170 			context.deleteBuffer(s.arrayBuffer)
    171 		}
    172 		if !s.elementArrayBuffer.equal(zeroBuffer) {
    173 			context.deleteBuffer(s.elementArrayBuffer)
    174 		}
    175 	}
    176 
    177 	shaderVertexModelviewNative, err := context.newVertexShader(vertexShaderStr())
    178 	if err != nil {
    179 		panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
    180 	}
    181 	defer context.deleteShader(shaderVertexModelviewNative)
    182 
    183 	for _, c := range []bool{false, true} {
    184 		for _, a := range []driver.Address{
    185 			driver.AddressClampToZero,
    186 			driver.AddressRepeat,
    187 			driver.AddressUnsafe,
    188 		} {
    189 			for _, f := range []driver.Filter{
    190 				driver.FilterNearest,
    191 				driver.FilterLinear,
    192 				driver.FilterScreen,
    193 			} {
    194 				shaderFragmentColorMatrixNative, err := context.newFragmentShader(fragmentShaderStr(c, f, a))
    195 				if err != nil {
    196 					panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
    197 				}
    198 				defer context.deleteShader(shaderFragmentColorMatrixNative)
    199 
    200 				program, err := context.newProgram([]shader{
    201 					shaderVertexModelviewNative,
    202 					shaderFragmentColorMatrixNative,
    203 				}, theArrayBufferLayout.names())
    204 
    205 				if err != nil {
    206 					return err
    207 				}
    208 
    209 				s.programs[programKey{
    210 					useColorM: c,
    211 					filter:    f,
    212 					address:   a,
    213 				}] = program
    214 			}
    215 		}
    216 	}
    217 
    218 	s.arrayBuffer = theArrayBufferLayout.newArrayBuffer(context)
    219 
    220 	// Note that the indices passed to NewElementArrayBuffer is not under GC management
    221 	// in opengl package due to unsafe-way.
    222 	// See NewElementArrayBuffer in context_mobile.go.
    223 	s.elementArrayBuffer = context.newElementArrayBuffer(graphics.IndicesNum * 2)
    224 
    225 	return nil
    226 }
    227 
    228 // areSameFloat32Array returns a boolean indicating if a and b are deeply equal.
    229 func areSameFloat32Array(a, b []float32) bool {
    230 	if len(a) != len(b) {
    231 		return false
    232 	}
    233 	for i := 0; i < len(a); i++ {
    234 		if a[i] != b[i] {
    235 			return false
    236 		}
    237 	}
    238 	return true
    239 }
    240 
    241 type uniformVariable struct {
    242 	name  string
    243 	value interface{}
    244 	typ   shaderir.Type
    245 }
    246 
    247 type textureVariable struct {
    248 	valid  bool
    249 	native textureNative
    250 }
    251 
    252 // useProgram uses the program (programTexture).
    253 func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderImageNum]textureVariable) error {
    254 	if !g.state.lastProgram.equal(program) {
    255 		g.context.useProgram(program)
    256 		if g.state.lastProgram.equal(zeroProgram) {
    257 			theArrayBufferLayout.enable(&g.context)
    258 			g.context.bindArrayBuffer(g.state.arrayBuffer)
    259 			g.context.bindElementArrayBuffer(g.state.elementArrayBuffer)
    260 		}
    261 
    262 		g.state.lastProgram = program
    263 		for k := range g.state.lastUniforms {
    264 			delete(g.state.lastUniforms, k)
    265 		}
    266 		g.state.lastActiveTexture = 0
    267 		g.context.activeTexture(0)
    268 	}
    269 
    270 	for _, u := range uniforms {
    271 		switch v := u.value.(type) {
    272 		case float32:
    273 			if got, expected := (&shaderir.Type{Main: shaderir.Float}), &u.typ; !got.Equal(expected) {
    274 				return fmt.Errorf("opengl: uniform variable %s type doesn't match: expected %s but %s", u.name, expected.String(), got.String())
    275 			}
    276 
    277 			cached, ok := g.state.lastUniforms[u.name].(float32)
    278 			if ok && cached == v {
    279 				continue
    280 			}
    281 			// TODO: Remember whether the location is available or not.
    282 			g.context.uniformFloat(program, u.name, v)
    283 			g.state.lastUniforms[u.name] = v
    284 		case []float32:
    285 			if got, expected := len(v), u.typ.FloatNum(); got != expected {
    286 				return fmt.Errorf("opengl: length of a uniform variables %s (%s) doesn't match: expected %d but %d", u.name, u.typ.String(), expected, got)
    287 			}
    288 
    289 			cached, ok := g.state.lastUniforms[u.name].([]float32)
    290 			if ok && areSameFloat32Array(cached, v) {
    291 				continue
    292 			}
    293 			g.context.uniformFloats(program, u.name, v, u.typ)
    294 			g.state.lastUniforms[u.name] = v
    295 		default:
    296 			return fmt.Errorf("opengl: unexpected uniform value: %v (type: %T)", u.value, u.value)
    297 		}
    298 	}
    299 
    300 	type activatedTexture struct {
    301 		textureNative textureNative
    302 		index         int
    303 	}
    304 
    305 	// textureNative cannot be a map key unfortunately.
    306 	textureToActivatedTexture := []activatedTexture{}
    307 	var idx int
    308 loop:
    309 	for i, t := range textures {
    310 		if !t.valid {
    311 			continue
    312 		}
    313 
    314 		// If the texture is already bound, set the texture variable to point to the texture.
    315 		// Rebinding the same texture seems problematic (#1193).
    316 		for _, at := range textureToActivatedTexture {
    317 			if t.native.equal(at.textureNative) {
    318 				g.context.uniformInt(program, fmt.Sprintf("T%d", i), at.index)
    319 				continue loop
    320 			}
    321 		}
    322 
    323 		textureToActivatedTexture = append(textureToActivatedTexture, activatedTexture{
    324 			textureNative: t.native,
    325 			index:         idx,
    326 		})
    327 		g.context.uniformInt(program, fmt.Sprintf("T%d", i), idx)
    328 		if g.state.lastActiveTexture != idx {
    329 			g.context.activeTexture(idx)
    330 			g.state.lastActiveTexture = idx
    331 		}
    332 
    333 		// Apparently, a texture must be bound every time. The cache is not used here.
    334 		g.context.bindTexture(t.native)
    335 
    336 		idx++
    337 	}
    338 
    339 	return nil
    340 }