zorldo

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

graphics.go (10655B)


      1 // Copyright 2018 The Ebiten Authors
      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 )
     24 
     25 var theGraphics Graphics
     26 
     27 func Get() *Graphics {
     28 	return &theGraphics
     29 }
     30 
     31 type Graphics struct {
     32 	state   openGLState
     33 	context context
     34 
     35 	nextImageID driver.ImageID
     36 	images      map[driver.ImageID]*Image
     37 
     38 	nextShaderID driver.ShaderID
     39 	shaders      map[driver.ShaderID]*Shader
     40 
     41 	// drawCalled is true just after Draw is called. This holds true until ReplacePixels is called.
     42 	drawCalled bool
     43 }
     44 
     45 func (g *Graphics) Begin() {
     46 	// Do nothing.
     47 }
     48 
     49 func (g *Graphics) End() {
     50 	// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
     51 	// TODO: examples/sprites worked without this. Is this really needed?
     52 	g.context.flush()
     53 }
     54 
     55 func (g *Graphics) SetTransparent(transparent bool) {
     56 	// Do nothings.
     57 }
     58 
     59 func (g *Graphics) checkSize(width, height int) {
     60 	if width < 1 {
     61 		panic(fmt.Sprintf("opengl: width (%d) must be equal or more than %d", width, 1))
     62 	}
     63 	if height < 1 {
     64 		panic(fmt.Sprintf("opengl: height (%d) must be equal or more than %d", height, 1))
     65 	}
     66 	m := g.context.getMaxTextureSize()
     67 	if width > m {
     68 		panic(fmt.Sprintf("opengl: width (%d) must be less than or equal to %d", width, m))
     69 	}
     70 	if height > m {
     71 		panic(fmt.Sprintf("opengl: height (%d) must be less than or equal to %d", height, m))
     72 	}
     73 }
     74 
     75 func (g *Graphics) genNextImageID() driver.ImageID {
     76 	g.nextImageID++
     77 	return g.nextImageID
     78 }
     79 
     80 func (g *Graphics) genNextShaderID() driver.ShaderID {
     81 	g.nextShaderID++
     82 	return g.nextShaderID
     83 }
     84 
     85 func (g *Graphics) NewImage(width, height int) (driver.Image, error) {
     86 	i := &Image{
     87 		id:       g.genNextImageID(),
     88 		graphics: g,
     89 		width:    width,
     90 		height:   height,
     91 	}
     92 	w := graphics.InternalImageSize(width)
     93 	h := graphics.InternalImageSize(height)
     94 	g.checkSize(w, h)
     95 	t, err := g.context.newTexture(w, h)
     96 	if err != nil {
     97 		return nil, err
     98 	}
     99 	i.texture = t
    100 	g.addImage(i)
    101 	return i, nil
    102 }
    103 
    104 func (g *Graphics) NewScreenFramebufferImage(width, height int) (driver.Image, error) {
    105 	g.checkSize(width, height)
    106 	i := &Image{
    107 		id:       g.genNextImageID(),
    108 		graphics: g,
    109 		width:    width,
    110 		height:   height,
    111 		screen:   true,
    112 	}
    113 	g.addImage(i)
    114 	return i, nil
    115 }
    116 
    117 func (g *Graphics) addImage(img *Image) {
    118 	if g.images == nil {
    119 		g.images = map[driver.ImageID]*Image{}
    120 	}
    121 	if _, ok := g.images[img.id]; ok {
    122 		panic(fmt.Sprintf("opengl: image ID %d was already registered", img.id))
    123 	}
    124 	g.images[img.id] = img
    125 }
    126 
    127 func (g *Graphics) removeImage(img *Image) {
    128 	delete(g.images, img.id)
    129 }
    130 
    131 func (g *Graphics) Initialize() error {
    132 	return g.state.reset(&g.context)
    133 }
    134 
    135 // Reset resets or initializes the current OpenGL state.
    136 func (g *Graphics) Reset() error {
    137 	return g.state.reset(&g.context)
    138 }
    139 
    140 func (g *Graphics) SetVertices(vertices []float32, indices []uint16) {
    141 	// Note that the vertices passed to BufferSubData is not under GC management
    142 	// in opengl package due to unsafe-way.
    143 	// See BufferSubData in context_mobile.go.
    144 	g.context.arrayBufferSubData(vertices)
    145 	g.context.elementArrayBufferSubData(indices)
    146 }
    147 
    148 func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM driver.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error {
    149 	destination := g.images[dstID]
    150 
    151 	g.drawCalled = true
    152 
    153 	if err := destination.setViewport(); err != nil {
    154 		return err
    155 	}
    156 	g.context.scissor(
    157 		int(dstRegion.X),
    158 		int(dstRegion.Y),
    159 		int(dstRegion.Width),
    160 		int(dstRegion.Height),
    161 	)
    162 	g.context.blendFunc(mode)
    163 
    164 	var program program
    165 	var uniformVars []uniformVariable
    166 	if shaderID == driver.InvalidShaderID {
    167 		program = g.state.programs[programKey{
    168 			useColorM: !colorM.IsIdentity(),
    169 			filter:    filter,
    170 			address:   address,
    171 		}]
    172 
    173 		dw, dh := destination.framebufferSize()
    174 		uniformVars = append(uniformVars, uniformVariable{
    175 			name:  "viewport_size",
    176 			value: []float32{float32(dw), float32(dh)},
    177 			typ:   shaderir.Type{Main: shaderir.Vec2},
    178 		}, uniformVariable{
    179 			name: "source_region",
    180 			value: []float32{
    181 				srcRegion.X,
    182 				srcRegion.Y,
    183 				srcRegion.X + srcRegion.Width,
    184 				srcRegion.Y + srcRegion.Height,
    185 			},
    186 			typ: shaderir.Type{Main: shaderir.Vec4},
    187 		})
    188 
    189 		if !colorM.IsIdentity() {
    190 			// ColorM's elements are immutable. It's OK to hold the reference without copying.
    191 			var esBody [16]float32
    192 			var esTranslate [4]float32
    193 			colorM.Elements(&esBody, &esTranslate)
    194 			uniformVars = append(uniformVars, uniformVariable{
    195 				name:  "color_matrix_body",
    196 				value: esBody[:],
    197 				typ:   shaderir.Type{Main: shaderir.Mat4},
    198 			}, uniformVariable{
    199 				name:  "color_matrix_translation",
    200 				value: esTranslate[:],
    201 				typ:   shaderir.Type{Main: shaderir.Vec4},
    202 			})
    203 		}
    204 
    205 		if filter != driver.FilterNearest {
    206 			sw, sh := g.images[srcIDs[0]].framebufferSize()
    207 			uniformVars = append(uniformVars, uniformVariable{
    208 				name:  "source_size",
    209 				value: []float32{float32(sw), float32(sh)},
    210 				typ:   shaderir.Type{Main: shaderir.Vec2},
    211 			})
    212 		}
    213 
    214 		if filter == driver.FilterScreen {
    215 			scale := float32(destination.width) / float32(g.images[srcIDs[0]].width)
    216 			uniformVars = append(uniformVars, uniformVariable{
    217 				name:  "scale",
    218 				value: scale,
    219 				typ:   shaderir.Type{Main: shaderir.Float},
    220 			})
    221 		}
    222 	} else {
    223 		shader := g.shaders[shaderID]
    224 		program = shader.p
    225 		uniformVars = make([]uniformVariable, graphics.PreservedUniformVariablesNum+len(uniforms))
    226 
    227 		{
    228 			const idx = graphics.DestinationTextureSizeUniformVariableIndex
    229 			w, h := destination.framebufferSize()
    230 			uniformVars[idx].name = fmt.Sprintf("U%d", idx)
    231 			uniformVars[idx].value = []float32{float32(w), float32(h)}
    232 			uniformVars[idx].typ = shader.ir.Uniforms[idx]
    233 		}
    234 		{
    235 			sizes := make([]float32, 2*len(srcIDs))
    236 			for i, srcID := range srcIDs {
    237 				if img := g.images[srcID]; img != nil {
    238 					w, h := img.framebufferSize()
    239 					sizes[2*i] = float32(w)
    240 					sizes[2*i+1] = float32(h)
    241 				}
    242 
    243 			}
    244 			const idx = graphics.TextureSizesUniformVariableIndex
    245 			uniformVars[idx].name = fmt.Sprintf("U%d", idx)
    246 			uniformVars[idx].value = sizes
    247 			uniformVars[idx].typ = shader.ir.Uniforms[idx]
    248 		}
    249 		dw, dh := destination.framebufferSize()
    250 		{
    251 			origin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
    252 			const idx = graphics.TextureDestinationRegionOriginUniformVariableIndex
    253 			uniformVars[idx].name = fmt.Sprintf("U%d", idx)
    254 			uniformVars[idx].value = origin
    255 			uniformVars[idx].typ = shader.ir.Uniforms[idx]
    256 		}
    257 		{
    258 			size := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
    259 			const idx = graphics.TextureDestinationRegionSizeUniformVariableIndex
    260 			uniformVars[idx].name = fmt.Sprintf("U%d", idx)
    261 			uniformVars[idx].value = size
    262 			uniformVars[idx].typ = shader.ir.Uniforms[idx]
    263 		}
    264 		{
    265 			voffsets := make([]float32, 2*len(offsets))
    266 			for i, o := range offsets {
    267 				voffsets[2*i] = o[0]
    268 				voffsets[2*i+1] = o[1]
    269 			}
    270 			const idx = graphics.TextureSourceOffsetsUniformVariableIndex
    271 			uniformVars[idx].name = fmt.Sprintf("U%d", idx)
    272 			uniformVars[idx].value = voffsets
    273 			uniformVars[idx].typ = shader.ir.Uniforms[idx]
    274 		}
    275 		{
    276 			origin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
    277 			const idx = graphics.TextureSourceRegionOriginUniformVariableIndex
    278 			uniformVars[idx].name = fmt.Sprintf("U%d", idx)
    279 			uniformVars[idx].value = origin
    280 			uniformVars[idx].typ = shader.ir.Uniforms[idx]
    281 		}
    282 		{
    283 			size := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
    284 			const idx = graphics.TextureSourceRegionSizeUniformVariableIndex
    285 			uniformVars[idx].name = fmt.Sprintf("U%d", idx)
    286 			uniformVars[idx].value = size
    287 			uniformVars[idx].typ = shader.ir.Uniforms[idx]
    288 		}
    289 
    290 		for i, v := range uniforms {
    291 			const offset = graphics.PreservedUniformVariablesNum
    292 			uniformVars[i+offset].name = fmt.Sprintf("U%d", i+offset)
    293 			uniformVars[i+offset].value = v
    294 			uniformVars[i+offset].typ = shader.ir.Uniforms[i+offset]
    295 		}
    296 	}
    297 
    298 	var imgs [graphics.ShaderImageNum]textureVariable
    299 	for i, srcID := range srcIDs {
    300 		if srcID == driver.InvalidImageID {
    301 			continue
    302 		}
    303 		imgs[i].valid = true
    304 		imgs[i].native = g.images[srcID].texture
    305 	}
    306 
    307 	if err := g.useProgram(program, uniformVars, imgs); err != nil {
    308 		return err
    309 	}
    310 
    311 	if evenOdd {
    312 		if err := destination.ensureStencilBuffer(); err != nil {
    313 			return err
    314 		}
    315 		g.context.enableStencilTest()
    316 		g.context.beginStencilWithEvenOddRule()
    317 		g.context.drawElements(indexLen, indexOffset*2)
    318 		g.context.endStencilWithEvenOddRule()
    319 	}
    320 	g.context.drawElements(indexLen, indexOffset*2) // 2 is uint16 size in bytes
    321 	if evenOdd {
    322 		g.context.disableStencilTest()
    323 	}
    324 
    325 	return nil
    326 }
    327 
    328 func (g *Graphics) SetVsyncEnabled(enabled bool) {
    329 	// Do nothing
    330 }
    331 
    332 func (g *Graphics) SetFullscreen(fullscreen bool) {
    333 	// Do nothing
    334 }
    335 
    336 func (g *Graphics) FramebufferYDirection() driver.YDirection {
    337 	return driver.Upward
    338 }
    339 
    340 func (g *Graphics) NeedsRestoring() bool {
    341 	return g.context.needsRestoring()
    342 }
    343 
    344 func (g *Graphics) NeedsClearingScreen() bool {
    345 	return true
    346 }
    347 
    348 func (g *Graphics) IsGL() bool {
    349 	return true
    350 }
    351 
    352 func (g *Graphics) HasHighPrecisionFloat() bool {
    353 	return g.context.hasHighPrecisionFloat()
    354 }
    355 
    356 func (g *Graphics) MaxImageSize() int {
    357 	return g.context.getMaxTextureSize()
    358 }
    359 
    360 func (g *Graphics) NewShader(program *shaderir.Program) (driver.Shader, error) {
    361 	s, err := newShader(g.genNextShaderID(), g, program)
    362 	if err != nil {
    363 		return nil, err
    364 	}
    365 	g.addShader(s)
    366 	return s, nil
    367 }
    368 
    369 func (g *Graphics) addShader(shader *Shader) {
    370 	if g.shaders == nil {
    371 		g.shaders = map[driver.ShaderID]*Shader{}
    372 	}
    373 	if _, ok := g.shaders[shader.id]; ok {
    374 		panic(fmt.Sprintf("opengl: shader ID %d was already registered", shader.id))
    375 	}
    376 	g.shaders[shader.id] = shader
    377 }
    378 
    379 func (g *Graphics) removeShader(shader *Shader) {
    380 	delete(g.shaders, shader.id)
    381 }