twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

program.go (9238B)


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