internal/graphicscommand: refactoring: add preserved uniform variables at the graphicscommand package

This is a preparation to skip setting unnecessary uniform variables
like dstRegion.

Updates #2232
This commit is contained in:
Hajime Hoshi 2022-10-30 18:31:06 +09:00
parent 72bdd690a1
commit a5993f09a2
5 changed files with 102 additions and 204 deletions

View File

@ -108,22 +108,12 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
q.tmpNumVertexFloats += len(vertices) q.tmpNumVertexFloats += len(vertices)
q.tmpNumIndices += len(indices) q.tmpNumIndices += len(indices)
if srcs[0] != nil { uniforms = prependPreservedUniforms(uniforms, dst, srcs, offsets, dstRegion, srcRegion)
w, h := srcs[0].InternalSize()
srcRegion.X /= float32(w)
srcRegion.Y /= float32(h)
srcRegion.Width /= float32(w)
srcRegion.Height /= float32(h)
for i := range offsets {
offsets[i][0] /= float32(w)
offsets[i][1] /= float32(h)
}
}
// TODO: If dst is the screen, reorder the command to be the last. // TODO: If dst is the screen, reorder the command to be the last.
if !split && 0 < len(q.commands) { if !split && 0 < len(q.commands) {
if last, ok := q.commands[len(q.commands)-1].(*drawTrianglesCommand); ok { if last, ok := q.commands[len(q.commands)-1].(*drawTrianglesCommand); ok {
if last.CanMergeWithDrawTrianglesCommand(dst, srcs, vertices, blend, dstRegion, srcRegion, shader, uniforms, evenOdd) { if last.CanMergeWithDrawTrianglesCommand(dst, srcs, vertices, blend, dstRegion, shader, uniforms, evenOdd) {
last.setVertices(q.lastVertices(len(vertices) + last.numVertices())) last.setVertices(q.lastVertices(len(vertices) + last.numVertices()))
last.addNumIndices(len(indices)) last.addNumIndices(len(indices))
return return
@ -134,12 +124,10 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
c := q.drawTrianglesCommandPool.get() c := q.drawTrianglesCommandPool.get()
c.dst = dst c.dst = dst
c.srcs = srcs c.srcs = srcs
c.offsets = offsets
c.vertices = q.lastVertices(len(vertices)) c.vertices = q.lastVertices(len(vertices))
c.nindices = len(indices) c.nindices = len(indices)
c.blend = blend c.blend = blend
c.dstRegion = dstRegion c.dstRegion = dstRegion
c.srcRegion = srcRegion
c.shader = shader c.shader = shader
c.uniforms = uniforms c.uniforms = uniforms
c.evenOdd = evenOdd c.evenOdd = evenOdd
@ -258,12 +246,10 @@ func FlushCommands(graphicsDriver graphicsdriver.Graphics, endFrame bool) error
type drawTrianglesCommand struct { type drawTrianglesCommand struct {
dst *Image dst *Image
srcs [graphics.ShaderImageCount]*Image srcs [graphics.ShaderImageCount]*Image
offsets [graphics.ShaderImageCount - 1][2]float32
vertices []float32 vertices []float32
nindices int nindices int
blend graphicsdriver.Blend blend graphicsdriver.Blend
dstRegion graphicsdriver.Region dstRegion graphicsdriver.Region
srcRegion graphicsdriver.Region
shader *Shader shader *Shader
uniforms [][]float32 uniforms [][]float32
evenOdd bool evenOdd bool
@ -317,7 +303,7 @@ func (c *drawTrianglesCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
imgs[i] = src.image.ID() imgs[i] = src.image.ID()
} }
return graphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.offsets, c.shader.shader.ID(), c.nindices, indexOffset, c.blend, c.dstRegion, c.srcRegion, c.uniforms, c.evenOdd) return graphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.shader.shader.ID(), c.nindices, indexOffset, c.blend, c.dstRegion, c.uniforms, c.evenOdd)
} }
func (c *drawTrianglesCommand) numVertices() int { func (c *drawTrianglesCommand) numVertices() int {
@ -338,7 +324,7 @@ func (c *drawTrianglesCommand) addNumIndices(n int) {
// CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged // CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged
// with the drawTrianglesCommand c. // with the drawTrianglesCommand c.
func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageCount]*Image, vertices []float32, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms [][]float32, evenOdd bool) bool { func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageCount]*Image, vertices []float32, blend graphicsdriver.Blend, dstRegion graphicsdriver.Region, shader *Shader, uniforms [][]float32, evenOdd bool) bool {
if c.shader != shader { if c.shader != shader {
return false return false
} }
@ -367,9 +353,6 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs
if c.dstRegion != dstRegion { if c.dstRegion != dstRegion {
return false return false
} }
if c.srcRegion != srcRegion {
return false
}
if c.evenOdd || evenOdd { if c.evenOdd || evenOdd {
if c.evenOdd && evenOdd { if c.evenOdd && evenOdd {
return !mightOverlapDstRegions(c.vertices, vertices) return !mightOverlapDstRegions(c.vertices, vertices)
@ -572,3 +555,61 @@ func MaxImageSize(graphicsDriver graphicsdriver.Graphics) int {
}) })
return size return size
} }
func prependPreservedUniforms(uniforms [][]float32, dst *Image, srcs [graphics.ShaderImageCount]*Image, offsets [graphics.ShaderImageCount - 1][2]float32, dstRegion, srcRegion graphicsdriver.Region) [][]float32 {
uniforms = append(make([][]float32, graphics.PreservedUniformVariablesCount), uniforms...)
// Set the destination texture size.
dw, dh := dst.InternalSize()
uniforms[graphics.TextureDestinationSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)}
// Set the source texture sizes.
usizes := make([]float32, 2*len(srcs))
for i, src := range srcs {
if src != nil {
w, h := src.InternalSize()
usizes[2*i] = float32(w)
usizes[2*i+1] = float32(h)
}
}
uniforms[graphics.TextureSourceSizesUniformVariableIndex] = usizes
// Set the destination region.
// TODO: Set them only when the shader refers this (#2232).
uniforms[graphics.TextureDestinationRegionOriginUniformVariableIndex] = []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
uniforms[graphics.TextureDestinationRegionSizeUniformVariableIndex] = []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
if srcs[0] != nil {
w, h := srcs[0].InternalSize()
srcRegion.X /= float32(w)
srcRegion.Y /= float32(h)
srcRegion.Width /= float32(w)
srcRegion.Height /= float32(h)
for i := range offsets {
offsets[i][0] /= float32(w)
offsets[i][1] /= float32(h)
}
}
// Set the source offsets.
uoffsets := make([]float32, 2*len(offsets))
for i, offset := range offsets {
uoffsets[2*i] = offset[0]
uoffsets[2*i+1] = offset[1]
}
uniforms[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets
// Set the source region of texture0.
// TODO: Set them only when the shader refers this (#2232).
uniforms[graphics.TextureSourceRegionOriginUniformVariableIndex] = []float32{float32(srcRegion.X), float32(srcRegion.Y)}
uniforms[graphics.TextureSourceRegionSizeUniformVariableIndex] = []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
uniforms[graphics.ProjectionMatrixUniformVariableIndex] = []float32{
2 / float32(dw), 0, 0, 0,
0, 2 / float32(dh), 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
}
return uniforms
}

View File

@ -1168,7 +1168,7 @@ func (g *Graphics) NewShader(program *shaderir.Program) (graphicsdriver.Shader,
return s, nil return s, nil
} }
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.ShaderImageCount]graphicsdriver.ImageID, offsets [graphics.ShaderImageCount - 1][2]float32, shaderID graphicsdriver.ShaderID, indexLen int, indexOffset int, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, uniforms [][]float32, evenOdd bool) error { func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, indexLen int, indexOffset int, blend graphicsdriver.Blend, dstRegion graphicsdriver.Region, uniforms [][]float32, evenOdd bool) error {
if shaderID == graphicsdriver.InvalidShaderID { if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("directx: shader ID is invalid") return fmt.Errorf("directx: shader ID is invalid")
} }
@ -1217,45 +1217,15 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.Sh
shader := g.shaders[shaderID] shader := g.shaders[shaderID]
// TODO: This logic is very similar to Metal's. Let's unify them. // In DirectX, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
dw, dh := dst.internalSize() // match. Then, the Y direction must be inverted.
us := make([][]float32, graphics.PreservedUniformVariablesCount+len(uniforms)) const idx = graphics.ProjectionMatrixUniformVariableIndex
us[graphics.TextureDestinationSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)} uniforms[idx][1] *= -1
usizes := make([]float32, 2*len(srcs)) uniforms[idx][5] *= -1
for i, src := range srcImages { uniforms[idx][9] *= -1
if src != nil { uniforms[idx][13] *= -1
w, h := src.internalSize()
usizes[2*i] = float32(w)
usizes[2*i+1] = float32(h)
}
}
us[graphics.TextureSourceSizesUniformVariableIndex] = usizes
udorigin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
us[graphics.TextureDestinationRegionOriginUniformVariableIndex] = udorigin
udsize := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
us[graphics.TextureDestinationRegionSizeUniformVariableIndex] = udsize
uoffsets := make([]float32, 2*len(offsets))
for i, offset := range offsets {
uoffsets[2*i] = offset[0]
uoffsets[2*i+1] = offset[1]
}
us[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets
usorigin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
us[graphics.TextureSourceRegionOriginUniformVariableIndex] = usorigin
ussize := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
us[graphics.TextureSourceRegionSizeUniformVariableIndex] = ussize
us[graphics.ProjectionMatrixUniformVariableIndex] = []float32{
2 / float32(dw), 0, 0, 0,
0, -2 / float32(dh), 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1,
}
for i, u := range uniforms { flattenUniforms := shader.uniformsToFloat32s(uniforms)
us[graphics.PreservedUniformVariablesCount+i] = u
}
flattenUniforms := shader.uniformsToFloat32s(us)
w, h := dst.internalSize() w, h := dst.internalSize()
g.needFlushDrawCommandList = true g.needFlushDrawCommandList = true

View File

@ -52,7 +52,7 @@ type Graphics interface {
NewShader(program *shaderir.Program) (Shader, error) NewShader(program *shaderir.Program) (Shader, error)
// DrawTriangles draws an image onto another image with the given parameters. // DrawTriangles draws an image onto another image with the given parameters.
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageCount]ImageID, offsets [graphics.ShaderImageCount - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, blend Blend, dstRegion, srcRegion Region, uniforms [][]float32, evenOdd bool) error DrawTriangles(dst ImageID, srcs [graphics.ShaderImageCount]ImageID, shader ShaderID, indexLen int, indexOffset int, blend Blend, dstRegion Region, uniforms [][]float32, evenOdd bool) error
} }
// GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost. // GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost.

View File

@ -503,7 +503,7 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion graph
return nil return nil
} }
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, offsets [graphics.ShaderImageCount - 1][2]float32, shaderID graphicsdriver.ShaderID, indexLen int, indexOffset int, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, uniforms [][]float32, evenOdd bool) error { func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, indexLen int, indexOffset int, blend graphicsdriver.Blend, dstRegion graphicsdriver.Region, uniforms [][]float32, evenOdd bool) error {
if shaderID == graphicsdriver.InvalidShaderID { if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("metal: shader ID is invalid") return fmt.Errorf("metal: shader ID is invalid")
} }
@ -519,58 +519,20 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
srcs[i] = g.images[srcID] srcs[i] = g.images[srcID]
} }
uniformVars := make([][]float32, graphics.PreservedUniformVariablesCount+len(uniforms)) uniformVars := make([][]float32, len(uniforms))
// Set the destination texture size.
dw, dh := dst.internalSize()
uniformVars[graphics.TextureDestinationSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)}
// Set the source texture sizes.
usizes := make([]float32, 2*len(srcs))
for i, src := range srcs {
if src != nil {
w, h := src.internalSize()
usizes[2*i] = float32(w)
usizes[2*i+1] = float32(h)
}
}
uniformVars[graphics.TextureSourceSizesUniformVariableIndex] = usizes
// Set the destination region's origin.
udorigin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
uniformVars[graphics.TextureDestinationRegionOriginUniformVariableIndex] = udorigin
// Set the destination region's size.
udsize := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
uniformVars[graphics.TextureDestinationRegionSizeUniformVariableIndex] = udsize
// Set the source offsets.
uoffsets := make([]float32, 2*len(offsets))
for i, offset := range offsets {
uoffsets[2*i] = offset[0]
uoffsets[2*i+1] = offset[1]
}
uniformVars[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets
// Set the source region's origin of texture0.
usorigin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
uniformVars[graphics.TextureSourceRegionOriginUniformVariableIndex] = usorigin
// Set the source region's size of texture0.
ussize := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
uniformVars[graphics.TextureSourceRegionSizeUniformVariableIndex] = ussize
uniformVars[graphics.ProjectionMatrixUniformVariableIndex] = []float32{
2 / float32(dw), 0, 0, 0,
0, -2 / float32(dh), 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1,
}
// Set the additional uniform variables. // Set the additional uniform variables.
for i, v := range uniforms { for i, v := range uniforms {
const offset = graphics.PreservedUniformVariablesCount if i == graphics.ProjectionMatrixUniformVariableIndex {
t := g.shaders[shaderID].ir.Uniforms[offset+i] // In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
v[1] *= -1
v[5] *= -1
v[9] *= -1
v[13] *= -1
}
t := g.shaders[shaderID].ir.Uniforms[i]
switch t.Main { switch t.Main {
case shaderir.Mat3: case shaderir.Mat3:
// float3x3 requires 16-byte alignment (#2036). // float3x3 requires 16-byte alignment (#2036).
@ -578,7 +540,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
copy(v1[0:3], v[0:3]) copy(v1[0:3], v[0:3])
copy(v1[4:7], v[3:6]) copy(v1[4:7], v[3:6])
copy(v1[8:11], v[6:9]) copy(v1[8:11], v[6:9])
uniformVars[offset+i] = v1 uniformVars[i] = v1
case shaderir.Array: case shaderir.Array:
switch t.Sub[0].Main { switch t.Sub[0].Main {
case shaderir.Mat3: case shaderir.Mat3:
@ -590,12 +552,12 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
copy(v1[offset1+4:offset1+7], v[offset0+3:offset0+6]) copy(v1[offset1+4:offset1+7], v[offset0+3:offset0+6])
copy(v1[offset1+8:offset1+11], v[offset0+6:offset0+9]) copy(v1[offset1+8:offset1+11], v[offset0+6:offset0+9])
} }
uniformVars[offset+i] = v1 uniformVars[i] = v1
default: default:
uniformVars[offset+i] = v uniformVars[i] = v
} }
default: default:
uniformVars[offset+i] = v uniformVars[i] = v
} }
} }

View File

@ -180,7 +180,7 @@ func (g *Graphics) uniformVariableName(idx int) string {
return name return name
} }
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, offsets [graphics.ShaderImageCount - 1][2]float32, shaderID graphicsdriver.ShaderID, indexLen int, indexOffset int, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, uniforms [][]float32, evenOdd bool) error { func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, indexLen int, indexOffset int, blend graphicsdriver.Blend, dstRegion graphicsdriver.Region, uniforms [][]float32, evenOdd bool) error {
if shaderID == graphicsdriver.InvalidShaderID { if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("opengl: shader ID is invalid") return fmt.Errorf("opengl: shader ID is invalid")
} }
@ -203,101 +203,26 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
shader := g.shaders[shaderID] shader := g.shaders[shaderID]
program := shader.p program := shader.p
ulen := graphics.PreservedUniformVariablesCount + len(uniforms) ulen := len(uniforms)
if cap(g.uniformVars) < ulen { if cap(g.uniformVars) < ulen {
g.uniformVars = make([]uniformVariable, ulen) g.uniformVars = make([]uniformVariable, ulen)
} else { } else {
g.uniformVars = g.uniformVars[:ulen] g.uniformVars = g.uniformVars[:ulen]
} }
{
const idx = graphics.TextureDestinationSizeUniformVariableIndex
w, h := destination.framebufferSize()
g.uniformVars[idx].name = g.uniformVariableName(idx)
g.uniformVars[idx].value = []float32{float32(w), float32(h)}
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
{
sizes := make([]float32, 2*len(srcIDs))
for i, srcID := range srcIDs {
if img := g.images[srcID]; img != nil {
w, h := img.framebufferSize()
sizes[2*i] = float32(w)
sizes[2*i+1] = float32(h)
}
}
const idx = graphics.TextureSourceSizesUniformVariableIndex
g.uniformVars[idx].name = g.uniformVariableName(idx)
g.uniformVars[idx].value = sizes
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
dw, dh := destination.framebufferSize()
{
origin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
const idx = graphics.TextureDestinationRegionOriginUniformVariableIndex
g.uniformVars[idx].name = g.uniformVariableName(idx)
g.uniformVars[idx].value = origin
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
{
size := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
const idx = graphics.TextureDestinationRegionSizeUniformVariableIndex
g.uniformVars[idx].name = g.uniformVariableName(idx)
g.uniformVars[idx].value = size
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
{
voffsets := make([]float32, 2*len(offsets))
for i, o := range offsets {
voffsets[2*i] = o[0]
voffsets[2*i+1] = o[1]
}
const idx = graphics.TextureSourceOffsetsUniformVariableIndex
g.uniformVars[idx].name = g.uniformVariableName(idx)
g.uniformVars[idx].value = voffsets
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
{
origin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
const idx = graphics.TextureSourceRegionOriginUniformVariableIndex
g.uniformVars[idx].name = g.uniformVariableName(idx)
g.uniformVars[idx].value = origin
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
{
size := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
const idx = graphics.TextureSourceRegionSizeUniformVariableIndex
g.uniformVars[idx].name = g.uniformVariableName(idx)
g.uniformVars[idx].value = size
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
{
const idx = graphics.ProjectionMatrixUniformVariableIndex
g.uniformVars[idx].name = g.uniformVariableName(idx)
if destination.screen {
g.uniformVars[idx].value = []float32{
2 / float32(dw), 0, 0, 0,
0, -2 / float32(dh), 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1,
}
} else {
g.uniformVars[idx].value = []float32{
2 / float32(dw), 0, 0, 0,
0, 2 / float32(dh), 0, 0,
0, 0, 1, 0,
-1, -1, 0, 1,
}
}
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
}
for i, v := range uniforms { for i, v := range uniforms {
const offset = graphics.PreservedUniformVariablesCount g.uniformVars[i].name = g.uniformVariableName(i)
g.uniformVars[i+offset].name = g.uniformVariableName(i + offset) g.uniformVars[i].value = v
g.uniformVars[i+offset].value = v g.uniformVars[i].typ = shader.ir.Uniforms[i]
g.uniformVars[i+offset].typ = shader.ir.Uniforms[i+offset] }
// In OpenGL, the NDC's Y direction (upward), so flip the Y direction for the final framebuffer.
if destination.screen {
const idx = graphics.ProjectionMatrixUniformVariableIndex
g.uniformVars[idx].value[1] *= -1
g.uniformVars[idx].value[5] *= -1
g.uniformVars[idx].value[9] *= -1
g.uniformVars[idx].value[13] *= -1
} }
var imgs [graphics.ShaderImageCount]textureVariable var imgs [graphics.ShaderImageCount]textureVariable