package kha.graphics4; import kha.arrays.ByteArray; import kha.arrays.Float32Array; import kha.Canvas; import kha.Color; import kha.FastFloat; import kha.Font; import kha.graphics2.ImageScaleQuality; import kha.Image; import kha.graphics4.BlendingOperation; import kha.graphics4.ConstantLocation; import kha.graphics4.CullMode; import kha.graphics4.IndexBuffer; import kha.graphics4.MipMapFilter; import kha.graphics4.TextureAddressing; import kha.graphics4.TextureFilter; import kha.graphics4.TextureFormat; import kha.graphics4.TextureUnit; import kha.graphics4.Usage; import kha.graphics4.VertexBuffer; import kha.graphics4.VertexData; import kha.graphics4.VertexStructure; import kha.math.FastMatrix3; import kha.math.FastMatrix4; import kha.math.FastVector2; import kha.math.Matrix3; import kha.math.Matrix4; import kha.math.Vector2; import kha.Shaders; import kha.simd.Float32x4; class InternalPipeline { public var pipeline: PipelineState; public var projectionLocation: ConstantLocation; public var textureLocation: TextureUnit; public function new(pipeline: PipelineState, projectionLocation: ConstantLocation, textureLocation: TextureUnit) { this.pipeline = pipeline; this.projectionLocation = projectionLocation; this.textureLocation = textureLocation; } } interface PipelineCache { function get(colorFormat: Array, depthStencilFormat: DepthStencilFormat): InternalPipeline; } class SimplePipelineCache implements PipelineCache { var pipeline: InternalPipeline; public function new(pipeline: PipelineState, texture: Bool) { var projectionLocation: ConstantLocation = null; try { projectionLocation = pipeline.getConstantLocation("projectionMatrix"); } catch (x:Dynamic) { trace(x); } var textureLocation: TextureUnit = null; if (texture) { try { textureLocation = pipeline.getTextureUnit("tex"); } catch (x:Dynamic) { trace(x); } } this.pipeline = new InternalPipeline(pipeline, projectionLocation, textureLocation); } public function get(colorFormats: Array, depthStencilFormat: DepthStencilFormat): InternalPipeline { return pipeline; } } class PerFramebufferPipelineCache implements PipelineCache { var pipelines: Array = []; public function new(pipeline: PipelineState, texture: Bool) { pipeline.compile(); var projectionLocation: ConstantLocation = null; try { projectionLocation = pipeline.getConstantLocation("projectionMatrix"); } catch (x:Dynamic) { trace(x); } var textureLocation: TextureUnit = null; if (texture) { try { textureLocation = pipeline.getTextureUnit("tex"); } catch (x:Dynamic) { trace(x); } } pipelines.push(new InternalPipeline(pipeline, projectionLocation, textureLocation)); } public function get(colorFormats: Array, depthStencilFormat: DepthStencilFormat): InternalPipeline { return pipelines[hash(colorFormats, depthStencilFormat)]; } function hash(colorFormats: Array, depthStencilFormat: DepthStencilFormat) { return 0; } } class ImageShaderPainter { var projectionMatrix: FastMatrix4; static var standardImagePipeline: PipelineCache = null; static var structure: VertexStructure = null; static inline var bufferSize: Int = 1500; static inline var vertexSize: Int = 6; static var bufferStart: Int; static var bufferIndex: Int; static var rectVertexBuffer: VertexBuffer; static var rectVertices: ByteArray; static var indexBuffer: IndexBuffer; static var lastTexture: Image; var bilinear: Bool = false; var bilinearMipmaps: Bool = false; var g: Graphics; var myPipeline: PipelineCache = null; public var pipeline(get, set): PipelineCache; public function new(g4: Graphics) { this.g = g4; bufferStart = 0; bufferIndex = 0; initShaders(); myPipeline = standardImagePipeline; initBuffers(); } function get_pipeline(): PipelineCache { return myPipeline; } function set_pipeline(pipe: PipelineCache): PipelineCache { myPipeline = pipe != null ? pipe : standardImagePipeline; return myPipeline; } public function setProjection(projectionMatrix: FastMatrix4): Void { this.projectionMatrix = projectionMatrix; } static function initShaders(): Void { if (structure == null) { structure = Graphics2.createImageVertexStructure(); } if (standardImagePipeline == null) { var pipeline = Graphics2.createImagePipeline(structure); standardImagePipeline = new PerFramebufferPipelineCache(pipeline, true); } } function initBuffers(): Void { if (rectVertexBuffer == null) { rectVertexBuffer = new VertexBuffer(bufferSize * 4, structure, Usage.DynamicUsage); rectVertices = rectVertexBuffer.lock(); indexBuffer = new IndexBuffer(bufferSize * 3 * 2, Usage.StaticUsage); var indices = indexBuffer.lock(); for (i in 0...bufferSize) { indices[i * 3 * 2 + 0] = i * 4 + 0; indices[i * 3 * 2 + 1] = i * 4 + 1; indices[i * 3 * 2 + 2] = i * 4 + 2; indices[i * 3 * 2 + 3] = i * 4 + 0; indices[i * 3 * 2 + 4] = i * 4 + 2; indices[i * 3 * 2 + 5] = i * 4 + 3; } indexBuffer.unlock(); } } inline function setRectVertices(bottomleftx: FastFloat, bottomlefty: FastFloat, topleftx: FastFloat, toplefty: FastFloat, toprightx: FastFloat, toprighty: FastFloat, bottomrightx: FastFloat, bottomrighty: FastFloat): Void { var baseIndex: Int = (bufferIndex - bufferStart) * vertexSize * 4 * 4; rectVertices.setFloat32(baseIndex + 0 * 4, bottomleftx); rectVertices.setFloat32(baseIndex + 1 * 4, bottomlefty); rectVertices.setFloat32(baseIndex + 2 * 4, -5.0); rectVertices.setFloat32(baseIndex + 6 * 4, topleftx); rectVertices.setFloat32(baseIndex + 7 * 4, toplefty); rectVertices.setFloat32(baseIndex + 8 * 4, -5.0); rectVertices.setFloat32(baseIndex + 12 * 4, toprightx); rectVertices.setFloat32(baseIndex + 13 * 4, toprighty); rectVertices.setFloat32(baseIndex + 14 * 4, -5.0); rectVertices.setFloat32(baseIndex + 18 * 4, bottomrightx); rectVertices.setFloat32(baseIndex + 19 * 4, bottomrighty); rectVertices.setFloat32(baseIndex + 20 * 4, -5.0); } inline function setRectTexCoords(left: FastFloat, top: FastFloat, right: FastFloat, bottom: FastFloat): Void { var baseIndex: Int = (bufferIndex - bufferStart) * vertexSize * 4 * 4; rectVertices.setFloat32(baseIndex + 3 * 4, left); rectVertices.setFloat32(baseIndex + 4 * 4, bottom); rectVertices.setFloat32(baseIndex + 9 * 4, left); rectVertices.setFloat32(baseIndex + 10 * 4, top); rectVertices.setFloat32(baseIndex + 15 * 4, right); rectVertices.setFloat32(baseIndex + 16 * 4, top); rectVertices.setFloat32(baseIndex + 21 * 4, right); rectVertices.setFloat32(baseIndex + 22 * 4, bottom); } inline function setRectColor(r: FastFloat, g: FastFloat, b: FastFloat, a: FastFloat): Void { var baseIndex: Int = (bufferIndex - bufferStart) * vertexSize * 4 * 4; rectVertices.setUint8(baseIndex + 5 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 5 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 5 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 5 * 4 + 3, Std.int(a * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 3, Std.int(a * 255)); rectVertices.setUint8(baseIndex + 17 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 17 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 17 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 17 * 4 + 3, Std.int(a * 255)); rectVertices.setUint8(baseIndex + 23 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 23 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 23 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 23 * 4 + 3, Std.int(a * 255)); } function drawBuffer(end: Bool): Void { if (bufferIndex - bufferStart == 0) { return; } rectVertexBuffer.unlock((bufferIndex - bufferStart) * 4); var pipeline = myPipeline.get(null, Depth24Stencil8); g.setPipeline(pipeline.pipeline); g.setVertexBuffer(rectVertexBuffer); g.setIndexBuffer(indexBuffer); g.setTexture(pipeline.textureLocation, lastTexture); g.setTextureParameters(pipeline.textureLocation, TextureAddressing.Clamp, TextureAddressing.Clamp, bilinear ? TextureFilter.LinearFilter : TextureFilter.PointFilter, bilinear ? TextureFilter.LinearFilter : TextureFilter.PointFilter, bilinearMipmaps ? MipMapFilter.LinearMipFilter : MipMapFilter.NoMipFilter); g.setMatrix(pipeline.projectionLocation, projectionMatrix); g.drawIndexedVertices(bufferStart * 2 * 3, (bufferIndex - bufferStart) * 2 * 3); g.setTexture(pipeline.textureLocation, null); if (end || (bufferStart + bufferIndex + 1) * 4 >= bufferSize) { bufferStart = 0; bufferIndex = 0; rectVertices = rectVertexBuffer.lock(0); } else { bufferStart = bufferIndex; rectVertices = rectVertexBuffer.lock(bufferStart * 4); } } public function setBilinearFilter(bilinear: Bool): Void { drawBuffer(false); lastTexture = null; this.bilinear = bilinear; } public function setBilinearMipmapFilter(bilinear: Bool): Void { drawBuffer(false); lastTexture = null; this.bilinearMipmaps = bilinear; } public inline function drawImage(img: kha.Image, bottomleftx: FastFloat, bottomlefty: FastFloat, topleftx: FastFloat, toplefty: FastFloat, toprightx: FastFloat, toprighty: FastFloat, bottomrightx: FastFloat, bottomrighty: FastFloat, opacity: FastFloat, color: Color): Void { var tex = img; if (bufferStart + bufferIndex + 1 >= bufferSize || (lastTexture != null && tex != lastTexture)) drawBuffer(false); setRectColor(color.R, color.G, color.B, color.A * opacity); setRectTexCoords(0, 0, tex.width / tex.realWidth, tex.height / tex.realHeight); setRectVertices(bottomleftx, bottomlefty, topleftx, toplefty, toprightx, toprighty, bottomrightx, bottomrighty); ++bufferIndex; lastTexture = tex; } public inline function drawImage2(img: kha.Image, sx: FastFloat, sy: FastFloat, sw: FastFloat, sh: FastFloat, bottomleftx: FastFloat, bottomlefty: FastFloat, topleftx: FastFloat, toplefty: FastFloat, toprightx: FastFloat, toprighty: FastFloat, bottomrightx: FastFloat, bottomrighty: FastFloat, opacity: FastFloat, color: Color): Void { var tex = img; if (bufferStart + bufferIndex + 1 >= bufferSize || (lastTexture != null && tex != lastTexture)) drawBuffer(false); setRectTexCoords(sx / tex.realWidth, sy / tex.realHeight, (sx + sw) / tex.realWidth, (sy + sh) / tex.realHeight); setRectColor(color.R, color.G, color.B, color.A * opacity); setRectVertices(bottomleftx, bottomlefty, topleftx, toplefty, toprightx, toprighty, bottomrightx, bottomrighty); ++bufferIndex; lastTexture = tex; } public inline function drawImageScale(img: kha.Image, sx: FastFloat, sy: FastFloat, sw: FastFloat, sh: FastFloat, left: FastFloat, top: FastFloat, right: FastFloat, bottom: FastFloat, opacity: FastFloat, color: Color): Void { var tex = img; if (bufferStart + bufferIndex + 1 >= bufferSize || (lastTexture != null && tex != lastTexture)) drawBuffer(false); setRectTexCoords(sx / tex.realWidth, sy / tex.realHeight, (sx + sw) / tex.realWidth, (sy + sh) / tex.realHeight); setRectColor(color.R, color.G, color.B, opacity); setRectVertices(left, bottom, left, top, right, top, right, bottom); ++bufferIndex; lastTexture = tex; } public function end(): Void { if (bufferIndex > 0) { drawBuffer(true); } lastTexture = null; } } class ColoredShaderPainter { var projectionMatrix: FastMatrix4; static var standardColorPipeline: PipelineCache = null; static var structure: VertexStructure = null; static inline var bufferSize: Int = 1000; static var bufferIndex: Int; static var rectVertexBuffer: VertexBuffer; static var rectVertices: Float32Array; static var indexBuffer: IndexBuffer; static inline var triangleBufferSize: Int = 1000; static var triangleBufferIndex: Int; static var triangleVertexBuffer: VertexBuffer; static var triangleVertices: Float32Array; static var triangleIndexBuffer: IndexBuffer; var g: Graphics; var myPipeline: PipelineCache = null; public var pipeline(get, set): PipelineCache; public function new(g4: Graphics) { this.g = g4; bufferIndex = 0; triangleBufferIndex = 0; initShaders(); myPipeline = standardColorPipeline; initBuffers(); } function get_pipeline(): PipelineCache { return myPipeline; } function set_pipeline(pipe: PipelineCache): PipelineCache { myPipeline = pipe != null ? pipe : standardColorPipeline; return myPipeline; } public function setProjection(projectionMatrix: FastMatrix4): Void { this.projectionMatrix = projectionMatrix; } static function initShaders(): Void { if (structure == null) { structure = Graphics2.createColoredVertexStructure(); } if (standardColorPipeline == null) { var pipeline = Graphics2.createColoredPipeline(structure); standardColorPipeline = new PerFramebufferPipelineCache(pipeline, false); } } function initBuffers(): Void { if (rectVertexBuffer == null) { rectVertexBuffer = new VertexBuffer(bufferSize * 4, structure, Usage.DynamicUsage); rectVertices = rectVertexBuffer.lock(); indexBuffer = new IndexBuffer(bufferSize * 3 * 2, Usage.StaticUsage); var indices = indexBuffer.lock(); for (i in 0...bufferSize) { indices[i * 3 * 2 + 0] = i * 4 + 0; indices[i * 3 * 2 + 1] = i * 4 + 1; indices[i * 3 * 2 + 2] = i * 4 + 2; indices[i * 3 * 2 + 3] = i * 4 + 0; indices[i * 3 * 2 + 4] = i * 4 + 2; indices[i * 3 * 2 + 5] = i * 4 + 3; } indexBuffer.unlock(); triangleVertexBuffer = new VertexBuffer(triangleBufferSize * 3, structure, Usage.DynamicUsage); triangleVertices = triangleVertexBuffer.lock(); triangleIndexBuffer = new IndexBuffer(triangleBufferSize * 3, Usage.StaticUsage); var triIndices = triangleIndexBuffer.lock(); for (i in 0...bufferSize) { triIndices[i * 3 + 0] = i * 3 + 0; triIndices[i * 3 + 1] = i * 3 + 1; triIndices[i * 3 + 2] = i * 3 + 2; } triangleIndexBuffer.unlock(); } } public function setRectVertices(bottomleftx: Float, bottomlefty: Float, topleftx: Float, toplefty: Float, toprightx: Float, toprighty: Float, bottomrightx: Float, bottomrighty: Float): Void { var baseIndex: Int = bufferIndex * 4 * 4; rectVertices.set(baseIndex + 0, bottomleftx); rectVertices.set(baseIndex + 1, bottomlefty); rectVertices.set(baseIndex + 2, -5.0); rectVertices.set(baseIndex + 4, topleftx); rectVertices.set(baseIndex + 5, toplefty); rectVertices.set(baseIndex + 6, -5.0); rectVertices.set(baseIndex + 8, toprightx); rectVertices.set(baseIndex + 9, toprighty); rectVertices.set(baseIndex + 10, -5.0); rectVertices.set(baseIndex + 12, bottomrightx); rectVertices.set(baseIndex + 13, bottomrighty); rectVertices.set(baseIndex + 14, -5.0); } public function setRectColors(opacity: FastFloat, color: Color): Void { var baseIndex: Int = bufferIndex * 4 * 4 * 4; var a: FastFloat = opacity * color.A; var r: FastFloat = a * color.R; var g: FastFloat = a * color.G; var b: FastFloat = a * color.B; rectVertices.setUint8(baseIndex + 3 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 3 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 3 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 3 * 4 + 3, Std.int(a * 255)); rectVertices.setUint8(baseIndex + 7 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 7 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 7 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 7 * 4 + 3, Std.int(a * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 11 * 4 + 3, Std.int(a * 255)); rectVertices.setUint8(baseIndex + 15 * 4 + 0, Std.int(r * 255)); rectVertices.setUint8(baseIndex + 15 * 4 + 1, Std.int(g * 255)); rectVertices.setUint8(baseIndex + 15 * 4 + 2, Std.int(b * 255)); rectVertices.setUint8(baseIndex + 15 * 4 + 3, Std.int(a * 255)); } function setTriVertices(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float): Void { var baseIndex: Int = triangleBufferIndex * 4 * 3; triangleVertices.set(baseIndex + 0, x1); triangleVertices.set(baseIndex + 1, y1); triangleVertices.set(baseIndex + 2, -5.0); triangleVertices.set(baseIndex + 4, x2); triangleVertices.set(baseIndex + 5, y2); triangleVertices.set(baseIndex + 6, -5.0); triangleVertices.set(baseIndex + 8, x3); triangleVertices.set(baseIndex + 9, y3); triangleVertices.set(baseIndex + 10, -5.0); } function setTriColors(opacity: FastFloat, color: Color): Void { var baseIndex: Int = triangleBufferIndex * 4 * 4 * 3; var a: FastFloat = opacity * color.A; var r: FastFloat = a * color.R; var g: FastFloat = a * color.G; var b: FastFloat = a * color.B; triangleVertices.setUint8(baseIndex + 3 * 4 + 0, Std.int(r * 255)); triangleVertices.setUint8(baseIndex + 3 * 4 + 1, Std.int(g * 255)); triangleVertices.setUint8(baseIndex + 3 * 4 + 2, Std.int(b * 255)); triangleVertices.setUint8(baseIndex + 3 * 4 + 3, Std.int(a * 255)); triangleVertices.setUint8(baseIndex + 7 * 4 + 0, Std.int(r * 255)); triangleVertices.setUint8(baseIndex + 7 * 4 + 1, Std.int(g * 255)); triangleVertices.setUint8(baseIndex + 7 * 4 + 2, Std.int(b * 255)); triangleVertices.setUint8(baseIndex + 7 * 4 + 3, Std.int(a * 255)); triangleVertices.setUint8(baseIndex + 11 * 4 + 0, Std.int(r * 255)); triangleVertices.setUint8(baseIndex + 11 * 4 + 1, Std.int(g * 255)); triangleVertices.setUint8(baseIndex + 11 * 4 + 2, Std.int(b * 255)); triangleVertices.setUint8(baseIndex + 11 * 4 + 3, Std.int(a * 255)); } function drawBuffer(trisDone: Bool): Void { if (bufferIndex == 0) { return; } if (!trisDone) endTris(true); rectVertexBuffer.unlock(bufferIndex * 4); var pipeline = myPipeline.get(null, Depth24Stencil8); g.setPipeline(pipeline.pipeline); g.setVertexBuffer(rectVertexBuffer); g.setIndexBuffer(indexBuffer); g.setMatrix(pipeline.projectionLocation, projectionMatrix); g.drawIndexedVertices(0, bufferIndex * 2 * 3); bufferIndex = 0; rectVertices = rectVertexBuffer.lock(); } function drawTriBuffer(rectsDone: Bool): Void { if (!rectsDone) endRects(true); triangleVertexBuffer.unlock(triangleBufferIndex * 3); var pipeline = myPipeline.get(null, Depth24Stencil8); g.setPipeline(pipeline.pipeline); g.setVertexBuffer(triangleVertexBuffer); g.setIndexBuffer(triangleIndexBuffer); g.setMatrix(pipeline.projectionLocation, projectionMatrix); g.drawIndexedVertices(0, triangleBufferIndex * 3); triangleBufferIndex = 0; triangleVertices = triangleVertexBuffer.lock(); } public function fillRect(opacity: FastFloat, color: Color, bottomleftx: Float, bottomlefty: Float, topleftx: Float, toplefty: Float, toprightx: Float, toprighty: Float, bottomrightx: Float, bottomrighty: Float): Void { if (triangleBufferIndex > 0) drawTriBuffer(true); // Flush other buffer for right render order if (bufferIndex + 1 >= bufferSize) drawBuffer(false); setRectColors(opacity, color); setRectVertices(bottomleftx, bottomlefty, topleftx, toplefty, toprightx, toprighty, bottomrightx, bottomrighty); ++bufferIndex; } public function fillTriangle(opacity: FastFloat, color: Color, x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) { if (bufferIndex > 0) drawBuffer(true); // Flush other buffer for right render order if (triangleBufferIndex + 1 >= triangleBufferSize) drawTriBuffer(false); setTriColors(opacity, color); setTriVertices(x1, y1, x2, y2, x3, y3); ++triangleBufferIndex; } public inline function endTris(rectsDone: Bool): Void { if (triangleBufferIndex > 0) drawTriBuffer(rectsDone); } public inline function endRects(trisDone: Bool): Void { if (bufferIndex > 0) drawBuffer(trisDone); } public inline function end(): Void { endTris(false); endRects(false); } } class TextShaderPainter { var projectionMatrix: FastMatrix4; static var standardTextPipeline: PipelineCache = null; static var structure: VertexStructure = null; static inline var bufferSize: Int = 1000; static var bufferIndex: Int; static var rectVertexBuffer: VertexBuffer; static var rectVertices: Float32Array; static var indexBuffer: IndexBuffer; var font: Kravur; static var lastTexture: Image; var g: Graphics; var myPipeline: PipelineCache = null; public var pipeline(get, set): PipelineCache; public var fontSize: Int; var bilinear: Bool = false; public function new(g4: Graphics) { this.g = g4; bufferIndex = 0; initShaders(); myPipeline = standardTextPipeline; initBuffers(); } function get_pipeline(): PipelineCache { return myPipeline; } function set_pipeline(pipe: PipelineCache): PipelineCache { myPipeline = pipe != null ? pipe : standardTextPipeline; return myPipeline; } public function setProjection(projectionMatrix: FastMatrix4): Void { this.projectionMatrix = projectionMatrix; } static function initShaders(): Void { if (structure == null) { structure = Graphics2.createTextVertexStructure(); } if (standardTextPipeline == null) { var pipeline = Graphics2.createTextPipeline(structure); standardTextPipeline = new PerFramebufferPipelineCache(pipeline, true); } } function initBuffers(): Void { if (rectVertexBuffer == null) { rectVertexBuffer = new VertexBuffer(bufferSize * 4, structure, Usage.DynamicUsage); rectVertices = rectVertexBuffer.lock(); indexBuffer = new IndexBuffer(bufferSize * 3 * 2, Usage.StaticUsage); var indices = indexBuffer.lock(); for (i in 0...bufferSize) { indices[i * 3 * 2 + 0] = i * 4 + 0; indices[i * 3 * 2 + 1] = i * 4 + 1; indices[i * 3 * 2 + 2] = i * 4 + 2; indices[i * 3 * 2 + 3] = i * 4 + 0; indices[i * 3 * 2 + 4] = i * 4 + 2; indices[i * 3 * 2 + 5] = i * 4 + 3; } indexBuffer.unlock(); } } function setRectVertices(bottomleftx: Float, bottomlefty: Float, topleftx: Float, toplefty: Float, toprightx: Float, toprighty: Float, bottomrightx: Float, bottomrighty: Float): Void { var baseIndex: Int = bufferIndex * 9 * 4; rectVertices.set(baseIndex + 0, bottomleftx); rectVertices.set(baseIndex + 1, bottomlefty); rectVertices.set(baseIndex + 2, -5.0); rectVertices.set(baseIndex + 9, topleftx); rectVertices.set(baseIndex + 10, toplefty); rectVertices.set(baseIndex + 11, -5.0); rectVertices.set(baseIndex + 18, toprightx); rectVertices.set(baseIndex + 19, toprighty); rectVertices.set(baseIndex + 20, -5.0); rectVertices.set(baseIndex + 27, bottomrightx); rectVertices.set(baseIndex + 28, bottomrighty); rectVertices.set(baseIndex + 29, -5.0); } function setRectTexCoords(left: Float, top: Float, right: Float, bottom: Float): Void { var baseIndex: Int = bufferIndex * 9 * 4; rectVertices.set(baseIndex + 3, left); rectVertices.set(baseIndex + 4, bottom); rectVertices.set(baseIndex + 12, left); rectVertices.set(baseIndex + 13, top); rectVertices.set(baseIndex + 21, right); rectVertices.set(baseIndex + 22, top); rectVertices.set(baseIndex + 30, right); rectVertices.set(baseIndex + 31, bottom); } function setRectColors(opacity: FastFloat, color: Color): Void { var baseIndex: Int = bufferIndex * 9 * 4; var a: FastFloat = opacity * color.A; rectVertices.set(baseIndex + 5, color.R); rectVertices.set(baseIndex + 6, color.G); rectVertices.set(baseIndex + 7, color.B); rectVertices.set(baseIndex + 8, a); rectVertices.set(baseIndex + 14, color.R); rectVertices.set(baseIndex + 15, color.G); rectVertices.set(baseIndex + 16, color.B); rectVertices.set(baseIndex + 17, a); rectVertices.set(baseIndex + 23, color.R); rectVertices.set(baseIndex + 24, color.G); rectVertices.set(baseIndex + 25, color.B); rectVertices.set(baseIndex + 26, a); rectVertices.set(baseIndex + 32, color.R); rectVertices.set(baseIndex + 33, color.G); rectVertices.set(baseIndex + 34, color.B); rectVertices.set(baseIndex + 35, a); } function drawBuffer(): Void { if (bufferIndex == 0) { return; } rectVertexBuffer.unlock(bufferIndex * 4); var pipeline = myPipeline.get(null, Depth24Stencil8); g.setPipeline(pipeline.pipeline); g.setVertexBuffer(rectVertexBuffer); g.setIndexBuffer(indexBuffer); g.setMatrix(pipeline.projectionLocation, projectionMatrix); g.setTexture(pipeline.textureLocation, lastTexture); g.setTextureParameters(pipeline.textureLocation, TextureAddressing.Clamp, TextureAddressing.Clamp, bilinear ? TextureFilter.LinearFilter : TextureFilter.PointFilter, bilinear ? TextureFilter.LinearFilter : TextureFilter.PointFilter, MipMapFilter.NoMipFilter); g.drawIndexedVertices(0, bufferIndex * 2 * 3); g.setTexture(pipeline.textureLocation, null); bufferIndex = 0; rectVertices = rectVertexBuffer.lock(); } public function setBilinearFilter(bilinear: Bool): Void { end(); this.bilinear = bilinear; } public function setFont(font: Font): Void { this.font = cast(font, Kravur); } static function findIndex(charCode: Int): Int { // var glyphs = kha.graphics2.Graphics.fontGlyphs; var blocks = Kravur.KravurImage.charBlocks; var offset = 0; for (i in 0...Std.int(blocks.length / 2)) { var start = blocks[i * 2]; var end = blocks[i * 2 + 1]; if (charCode >= start && charCode <= end) return offset + charCode - start; offset += end - start + 1; } return 0; } var bakedQuadCache = new kha.Kravur.AlignedQuad(); public function drawString(text: String, opacity: FastFloat, color: Color, x: Float, y: Float, transformation: FastMatrix3): Void { var font = this.font._get(fontSize); var tex = font.getTexture(); if (lastTexture != null && tex != lastTexture) drawBuffer(); lastTexture = tex; var xpos = x; var ypos = y; for (i in 0...text.length) { var charCode = StringTools.fastCodeAt(text, i); var q = font.getBakedQuad(bakedQuadCache, findIndex(charCode), xpos, ypos); if (q != null) { if (bufferIndex + 1 >= bufferSize) drawBuffer(); setRectColors(opacity, color); setRectTexCoords(q.s0 * tex.width / tex.realWidth, q.t0 * tex.height / tex.realHeight, q.s1 * tex.width / tex.realWidth, q.t1 * tex.height / tex.realHeight); var p0 = transformation.multvec(new FastVector2(q.x0, q.y1)); // bottom-left var p1 = transformation.multvec(new FastVector2(q.x0, q.y0)); // top-left var p2 = transformation.multvec(new FastVector2(q.x1, q.y0)); // top-right var p3 = transformation.multvec(new FastVector2(q.x1, q.y1)); // bottom-right setRectVertices(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); xpos += q.xadvance; ++bufferIndex; } } } public function drawCharacters(text: Array, start: Int, length: Int, opacity: FastFloat, color: Color, x: Float, y: Float, transformation: FastMatrix3): Void { var font = this.font._get(fontSize); var tex = font.getTexture(); if (lastTexture != null && tex != lastTexture) drawBuffer(); lastTexture = tex; var xpos = x; var ypos = y; for (i in start...start + length) { var q = font.getBakedQuad(bakedQuadCache, findIndex(text[i]), xpos, ypos); if (q != null) { if (bufferIndex + 1 >= bufferSize) drawBuffer(); setRectColors(opacity, color); setRectTexCoords(q.s0 * tex.width / tex.realWidth, q.t0 * tex.height / tex.realHeight, q.s1 * tex.width / tex.realWidth, q.t1 * tex.height / tex.realHeight); var p0 = transformation.multvec(new FastVector2(q.x0, q.y1)); // bottom-left var p1 = transformation.multvec(new FastVector2(q.x0, q.y0)); // top-left var p2 = transformation.multvec(new FastVector2(q.x1, q.y0)); // top-right var p3 = transformation.multvec(new FastVector2(q.x1, q.y1)); // bottom-right setRectVertices(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); xpos += q.xadvance; ++bufferIndex; } } } public function end(): Void { if (bufferIndex > 0) drawBuffer(); lastTexture = null; } } class Graphics2 extends kha.graphics2.Graphics { var myColor: Color; var myFont: Font; var projectionMatrix: FastMatrix4; public var imagePainter: ImageShaderPainter; var coloredPainter: ColoredShaderPainter; var textPainter: TextShaderPainter; public static var videoPipeline: PipelineState; var canvas: Canvas; var g: Graphics; static var current: Graphics2 = null; public function new(canvas: Canvas) { super(); color = Color.White; this.canvas = canvas; g = canvas.g4; imagePainter = new ImageShaderPainter(g); coloredPainter = new ColoredShaderPainter(g); textPainter = new TextShaderPainter(g); textPainter.fontSize = fontSize; projectionMatrix = FastMatrix4.identity(); setProjection(); if (videoPipeline == null) { videoPipeline = createImagePipeline(createImageVertexStructure()); videoPipeline.fragmentShader = Shaders.painter_video_frag; videoPipeline.vertexShader = Shaders.painter_video_vert; videoPipeline.compile(); } } static function upperPowerOfTwo(v: Int): Int { v--; v |= v >>> 1; v |= v >>> 2; v |= v >>> 4; v |= v >>> 8; v |= v >>> 16; v++; return v; } function setProjection(): Void { var width = canvas.width; var height = canvas.height; if (Std.isOfType(canvas, Framebuffer)) { projectionMatrix.setFrom(FastMatrix4.orthogonalProjection(0, width, height, 0, 0.1, 1000)); } else { if (!Image.nonPow2Supported) { width = upperPowerOfTwo(width); height = upperPowerOfTwo(height); } if (Image.renderTargetsInvertedY()) { projectionMatrix.setFrom(FastMatrix4.orthogonalProjection(0, width, 0, height, 0.1, 1000)); } else { projectionMatrix.setFrom(FastMatrix4.orthogonalProjection(0, width, height, 0, 0.1, 1000)); } } imagePainter.setProjection(projectionMatrix); coloredPainter.setProjection(projectionMatrix); textPainter.setProjection(projectionMatrix); } #if cpp public override function drawImage(img: kha.Image, x: FastFloat, y: FastFloat): Void { coloredPainter.end(); textPainter.end(); var xw: FastFloat = x + img.width; var yh: FastFloat = y + img.height; var xx = Float32x4.loadFast(x, x, xw, xw); var yy = Float32x4.loadFast(yh, y, y, yh); var _00 = Float32x4.loadAllFast(transformation._00); var _01 = Float32x4.loadAllFast(transformation._01); var _02 = Float32x4.loadAllFast(transformation._02); var _10 = Float32x4.loadAllFast(transformation._10); var _11 = Float32x4.loadAllFast(transformation._11); var _12 = Float32x4.loadAllFast(transformation._12); var _20 = Float32x4.loadAllFast(transformation._20); var _21 = Float32x4.loadAllFast(transformation._21); var _22 = Float32x4.loadAllFast(transformation._22); // matrix multiply var w = Float32x4.add(Float32x4.add(Float32x4.mul(_02, xx), Float32x4.mul(_12, yy)), _22); var px = Float32x4.div(Float32x4.add(Float32x4.add(Float32x4.mul(_00, xx), Float32x4.mul(_10, yy)), _20), w); var py = Float32x4.div(Float32x4.add(Float32x4.add(Float32x4.mul(_01, xx), Float32x4.mul(_11, yy)), _21), w); imagePainter.drawImage(img, Float32x4.get(px, 0), Float32x4.get(py, 0), Float32x4.get(px, 1), Float32x4.get(py, 1), Float32x4.get(px, 2), Float32x4.get(py, 2), Float32x4.get(px, 3), Float32x4.get(py, 3), opacity, this.color); } #else public override function drawImage(img: kha.Image, x: FastFloat, y: FastFloat): Void { coloredPainter.end(); textPainter.end(); var xw: FastFloat = x + img.width; var yh: FastFloat = y + img.height; var p1 = transformation.multvec(new FastVector2(x, yh)); var p2 = transformation.multvec(new FastVector2(x, y)); var p3 = transformation.multvec(new FastVector2(xw, y)); var p4 = transformation.multvec(new FastVector2(xw, yh)); imagePainter.drawImage(img, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, opacity, this.color); } #end public override function drawScaledSubImage(img: kha.Image, sx: FastFloat, sy: FastFloat, sw: FastFloat, sh: FastFloat, dx: FastFloat, dy: FastFloat, dw: FastFloat, dh: FastFloat): Void { coloredPainter.end(); textPainter.end(); var p1 = transformation.multvec(new FastVector2(dx, dy + dh)); var p2 = transformation.multvec(new FastVector2(dx, dy)); var p3 = transformation.multvec(new FastVector2(dx + dw, dy)); var p4 = transformation.multvec(new FastVector2(dx + dw, dy + dh)); imagePainter.drawImage2(img, sx, sy, sw, sh, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, opacity, this.color); } override function get_color(): Color { return myColor; } override function set_color(color: Color): Color { return myColor = color; } public override function drawRect(x: Float, y: Float, width: Float, height: Float, strength: Float = 1.0): Void { imagePainter.end(); textPainter.end(); var p1 = transformation.multvec(new FastVector2(x - strength / 2, y + strength / 2)); // bottom-left var p2 = transformation.multvec(new FastVector2(x - strength / 2, y - strength / 2)); // top-left var p3 = transformation.multvec(new FastVector2(x + width + strength / 2, y - strength / 2)); // top-right var p4 = transformation.multvec(new FastVector2(x + width + strength / 2, y + strength / 2)); // bottom-right coloredPainter.fillRect(opacity, color, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); // top p1.setFrom(transformation.multvec(new FastVector2(x - strength / 2, y + height - strength / 2))); p2.setFrom(transformation.multvec(new FastVector2(x - strength / 2, y + strength / 2))); p3.setFrom(transformation.multvec(new FastVector2(x + strength / 2, y + strength / 2))); p4.setFrom(transformation.multvec(new FastVector2(x + strength / 2, y + height - strength / 2))); coloredPainter.fillRect(opacity, color, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); // left p1.setFrom(transformation.multvec(new FastVector2(x - strength / 2, y + height + strength / 2))); p2.setFrom(transformation.multvec(new FastVector2(x - strength / 2, y + height - strength / 2))); p3.setFrom(transformation.multvec(new FastVector2(x + width + strength / 2, y + height - strength / 2))); p4.setFrom(transformation.multvec(new FastVector2(x + width + strength / 2, y + height + strength / 2))); coloredPainter.fillRect(opacity, color, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); // bottom p1.setFrom(transformation.multvec(new FastVector2(x + width - strength / 2, y + height - strength / 2))); p2.setFrom(transformation.multvec(new FastVector2(x + width - strength / 2, y + strength / 2))); p3.setFrom(transformation.multvec(new FastVector2(x + width + strength / 2, y + strength / 2))); p4.setFrom(transformation.multvec(new FastVector2(x + width + strength / 2, y + height - strength / 2))); coloredPainter.fillRect(opacity, color, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); // right } public override function fillRect(x: Float, y: Float, width: Float, height: Float): Void { imagePainter.end(); textPainter.end(); var p1 = transformation.multvec(new FastVector2(x, y + height)); var p2 = transformation.multvec(new FastVector2(x, y)); var p3 = transformation.multvec(new FastVector2(x + width, y)); var p4 = transformation.multvec(new FastVector2(x + width, y + height)); coloredPainter.fillRect(opacity, color, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y); } public override function drawString(text: String, x: Float, y: Float): Void { imagePainter.end(); coloredPainter.end(); textPainter.drawString(text, opacity, color, x, y, transformation); } public override function drawCharacters(text: Array, start: Int, length: Int, x: Float, y: Float): Void { imagePainter.end(); coloredPainter.end(); textPainter.drawCharacters(text, start, length, opacity, color, x, y, transformation); } override function get_font(): Font { return myFont; } override function set_font(font: Font): Font { textPainter.setFont(font); return myFont = font; } override function set_fontSize(value: Int): Int { return super.fontSize = textPainter.fontSize = value; } public override function drawLine(x1: Float, y1: Float, x2: Float, y2: Float, strength: Float = 1.0): Void { imagePainter.end(); textPainter.end(); var vec = new FastVector2(); if (y2 == y1) vec.setFrom(new FastVector2(0, -1)); else vec.setFrom(new FastVector2(1, -(x2 - x1) / (y2 - y1))); vec.length = strength; var p1 = new FastVector2(x1 + 0.5 * vec.x, y1 + 0.5 * vec.y); var p2 = new FastVector2(x2 + 0.5 * vec.x, y2 + 0.5 * vec.y); var p3 = p1.sub(vec); var p4 = p2.sub(vec); p1.setFrom(transformation.multvec(p1)); p2.setFrom(transformation.multvec(p2)); p3.setFrom(transformation.multvec(p3)); p4.setFrom(transformation.multvec(p4)); coloredPainter.fillTriangle(opacity, color, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); coloredPainter.fillTriangle(opacity, color, p3.x, p3.y, p2.x, p2.y, p4.x, p4.y); } public override function fillTriangle(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) { imagePainter.end(); textPainter.end(); var p1 = transformation.multvec(new FastVector2(x1, y1)); var p2 = transformation.multvec(new FastVector2(x2, y2)); var p3 = transformation.multvec(new FastVector2(x3, y3)); coloredPainter.fillTriangle(opacity, color, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); } var myImageScaleQuality: ImageScaleQuality = ImageScaleQuality.Low; override function get_imageScaleQuality(): ImageScaleQuality { return myImageScaleQuality; } override function set_imageScaleQuality(value: ImageScaleQuality): ImageScaleQuality { if (value == myImageScaleQuality) { return value; } imagePainter.setBilinearFilter(value == ImageScaleQuality.High); textPainter.setBilinearFilter(value == ImageScaleQuality.High); return myImageScaleQuality = value; } var myMipmapScaleQuality: ImageScaleQuality = ImageScaleQuality.Low; override function get_mipmapScaleQuality(): ImageScaleQuality { return myMipmapScaleQuality; } override function set_mipmapScaleQuality(value: ImageScaleQuality): ImageScaleQuality { imagePainter.setBilinearMipmapFilter(value == ImageScaleQuality.High); // textPainter.setBilinearMipmapFilter(value == ImageScaleQuality.High); // TODO (DK) implement for fonts as well? return myMipmapScaleQuality = value; } var pipelineCache = new Map(); var lastPipeline: PipelineState = null; override function setPipeline(pipeline: PipelineState): Void { if (pipeline == lastPipeline) { return; } lastPipeline = pipeline; flush(); if (pipeline == null) { imagePainter.pipeline = null; coloredPainter.pipeline = null; textPainter.pipeline = null; } else { var cache = pipelineCache[pipeline]; if (cache == null) { cache = new SimplePipelineCache(pipeline, true); pipelineCache[pipeline] = cache; } imagePainter.pipeline = cache; coloredPainter.pipeline = cache; textPainter.pipeline = cache; } } var scissorEnabled = false; var scissorX: Int = -1; var scissorY: Int = -1; var scissorW: Int = -1; var scissorH: Int = -1; override public function scissor(x: Int, y: Int, width: Int, height: Int): Void { // if (!scissorEnabled || x != scissorX || y != scissorY || width != scissorW || height != scissorH) { scissorEnabled = true; scissorX = x; scissorY = y; scissorW = width; scissorH = height; flush(); g.scissor(x, y, width, height); // } } override public function disableScissor(): Void { // if (scissorEnabled) { scissorEnabled = false; flush(); g.disableScissor(); // } } override public function begin(clear: Bool = true, clearColor: Color = null): Void { if (current == null) { current = this; } else { throw "End before you begin"; } g.begin(); if (clear) this.clear(clearColor); setProjection(); } override public function clear(color: Color = null): Void { flush(); g.clear(color == null ? Color.Black : color); } public override function flush(): Void { imagePainter.end(); textPainter.end(); coloredPainter.end(); } public override function end(): Void { flush(); g.end(); if (current == this) { current = null; } else { throw "Begin before you end"; } } function drawVideoInternal(video: kha.Video, x: Float, y: Float, width: Float, height: Float): Void {} override public function drawVideo(video: kha.Video, x: Float, y: Float, width: Float, height: Float): Void { setPipeline(videoPipeline); drawVideoInternal(video, x, y, width, height); setPipeline(null); } public static function createImageVertexStructure(): VertexStructure { var structure = new VertexStructure(); structure.add("vertexPosition", VertexData.Float32_3X); structure.add("vertexUV", VertexData.Float32_2X); structure.add("vertexColor", VertexData.UInt8_4X_Normalized); return structure; } public static function createImagePipeline(structure: VertexStructure): PipelineState { var shaderPipeline = new PipelineState(); shaderPipeline.fragmentShader = Shaders.painter_image_frag; shaderPipeline.vertexShader = Shaders.painter_image_vert; shaderPipeline.inputLayout = [structure]; shaderPipeline.blendSource = BlendingFactor.BlendOne; shaderPipeline.blendDestination = BlendingFactor.InverseSourceAlpha; shaderPipeline.alphaBlendSource = BlendingFactor.BlendOne; shaderPipeline.alphaBlendDestination = BlendingFactor.InverseSourceAlpha; return shaderPipeline; } public static function createColoredVertexStructure(): VertexStructure { var structure = new VertexStructure(); structure.add("vertexPosition", VertexData.Float32_3X); structure.add("vertexColor", VertexData.UInt8_4X_Normalized); return structure; } public static function createColoredPipeline(structure: VertexStructure): PipelineState { var shaderPipeline = new PipelineState(); shaderPipeline.fragmentShader = Shaders.painter_colored_frag; shaderPipeline.vertexShader = Shaders.painter_colored_vert; shaderPipeline.inputLayout = [structure]; shaderPipeline.blendSource = BlendingFactor.BlendOne; shaderPipeline.blendDestination = BlendingFactor.InverseSourceAlpha; shaderPipeline.alphaBlendSource = BlendingFactor.BlendOne; shaderPipeline.alphaBlendDestination = BlendingFactor.InverseSourceAlpha; return shaderPipeline; } public static function createTextVertexStructure(): VertexStructure { var structure = new VertexStructure(); structure.add("vertexPosition", VertexData.Float32_3X); structure.add("vertexUV", VertexData.Float32_2X); structure.add("vertexColor", VertexData.Float32_4X); return structure; } public static function createTextPipeline(structure: VertexStructure): PipelineState { var shaderPipeline = new PipelineState(); shaderPipeline.fragmentShader = Shaders.painter_text_frag; shaderPipeline.vertexShader = Shaders.painter_text_vert; shaderPipeline.inputLayout = [structure]; shaderPipeline.blendSource = BlendingFactor.SourceAlpha; shaderPipeline.blendDestination = BlendingFactor.InverseSourceAlpha; shaderPipeline.alphaBlendSource = BlendingFactor.SourceAlpha; shaderPipeline.alphaBlendDestination = BlendingFactor.InverseSourceAlpha; return shaderPipeline; } }