0c70c2da3076 — Leonard Ritter 3 months ago
* initial check-in sprite painter
4 files changed, 290 insertions(+), 1 deletions(-)

A => testing/test_spritepainter.sc
M testing/test_vertexpainter.sc
A => tukan/SpritePainter.sc
M tukan/gl.sc
A => testing/test_spritepainter.sc +71 -0
@@ 0,0 1,71 @@ 
+
+using import glm
+
+import ..tukan.use
+using import tukan.SpritePainter
+using import tukan.GLMain
+using import tukan.gl
+using import tukan.bitmap
+
+let glmain =
+    GLMain
+        title = "SpritePainter"
+        resizable = true
+
+global t = 0.0
+
+global painter = (SpritePainter)
+let spritesheet =
+    load-bitmap
+        .. module-dir "/data/7x4_font.png"
+        components = 4
+        #flip = false
+global texture_spritesheet = (GL.Texture2D)
+'setup texture_spritesheet
+    bitmap = spritesheet
+let sz = spritesheet.size
+va-map
+    inline (rc)
+        'register painter rc sz
+    ivec4 0 0 5 8
+    ivec4 6 0 (6 + 4) 8
+    ivec4 10 0 (10 + 5) 8
+
+@@ 'on GLMain.on-draw
+fn draw (time size glmain)
+    GL.BindFramebuffer GL.FRAMEBUFFER 0
+    GL.Viewport 0 0 (i32 size.x) (i32 size.y)
+    GL.ClearColor 0 0 0.5 1
+    GL.Clear
+        |
+            GL.COLOR_BUFFER_BIT
+            GL.DEPTH_BUFFER_BIT
+            GL.STENCIL_BUFFER_BIT
+
+    t += (/ 60.0)
+
+    let invsz = (8 * (/ 2 (vec2 size)))
+    painter.transform =
+        mat4
+            \ invsz.x 0 0 0
+            \ 0 invsz.y 0 0
+            \ 0 0 1 0
+            \ -1 -1 0 1
+
+    from (methodsof painter) let sprite
+    sprite 0 (vec2 0 0)
+        vec2 5 8
+        #angle = t
+    sprite 1 (vec2 6 0)
+        vec2 4 8
+        #angle = t
+    sprite 2 (vec2 11 0)
+        vec2 5 8
+        #angle = t
+    GL.BindTextureUnit 0 texture_spritesheet
+    'draw painter
+
+'run glmain
+
+;
+

          
M testing/test_vertexpainter.sc +1 -1
@@ 29,7 29,7 @@ fn draw (time size glmain)
 
     t += (/ 60.0)
 
-    from (methodsof painter) let clear color screen rotate AABB \
+    from (methodsof painter) let screen color screen rotate AABB \
         translate quad perspective scale light flush wireframe moveto lineto \
         closepath fill stroke
     # setup view

          
A => tukan/SpritePainter.sc +204 -0
@@ 0,0 1,204 @@ 
+
+using import struct
+using import enum
+using import glm
+using import Array
+using import Option
+using import Rc
+
+using import .gl
+using import .rotation
+using import .stage
+
+# 16 bytes
+struct Sprite plain
+    # uv coordinates within texture (0..1)
+    rect : vec4
+
+# 4 kilobytes
+#let ColorCount = 256
+#struct Palette plain
+    color : (array vec4 ColorCount)
+
+# 32 bytes
+struct DrawCommand plain
+    # target rectangle relative to pivot at 0,0
+    rect : vec4
+    # origin of pivot
+    origin : vec2
+    # rotation angle (0 = no rotation)
+    angle : f32
+    # index of sprite
+    sprite : u32
+
+let
+    # 1 MB worth of sprites (256x256)
+    MaxSpriteCount = (1 * (1 << 20) // (sizeof Sprite))
+    # 8 MB worth of draw commands per frame
+    MaxCommandCount = (8 * (1 << 20) // (sizeof DrawCommand))
+
+let shaders =
+    do
+        using import glsl
+
+        let
+            DrawCommandBinding = 0
+            SpriteBinding = 1
+            TransformLocation = 2
+            SamplerLocation = 3
+
+        buffer b_commands :
+            struct "DrawCommands" plain
+                elements : (array DrawCommand)
+            binding = DrawCommandBinding
+        buffer b_sprites :
+            struct "Sprites" plain
+                elements : (array Sprite)
+            binding = SpriteBinding
+        uniform u_transform : mat4
+            location = TransformLocation
+
+        inout io_uv : vec2
+
+        fn vertex-main ()
+            cmd := b_commands.elements @ gl_InstanceID
+            rect := (deref cmd.rect)
+            origin := (deref cmd.origin)
+            angle := (deref cmd.angle)
+            sprite := b_sprites.elements @ cmd.sprite
+            uv := (deref sprite.rect)
+            let vxid = (deref gl_VertexID)
+
+            let rotmtx =
+                do
+                    let ca sa = (cos angle) (sin angle)
+                    mat2 ca sa -sa ca
+            let pos uv =
+                switch vxid
+                case 0
+                    _ rect.xy uv.xy
+                case 1
+                    _ rect.zy uv.zy
+                case 2
+                    _ rect.zw uv.zw
+                case 3
+                    _ rect.xw uv.xw
+                default
+                    _ (vec2 0 0) (vec2 0 0)
+            io_uv.out = uv
+            gl_Position =
+                u_transform *
+                    vec4 (origin + rotmtx * pos) 0 1
+
+        uniform u_smp : sampler2D
+            location = SamplerLocation
+        out fragment-color : vec4
+
+        fn fragment-main ()
+            let uv = io_uv.in
+            fragment-color = (texture u_smp uv)
+
+        locals;
+
+struct SpritePainter
+    let SpriteArray = (Array Sprite)
+    let DrawCommandArray = (Array DrawCommand)
+
+    sprites : SpriteArray
+    commands : DrawCommandArray
+    buffer_sprites : GL.Buffer # Sprite
+    buffer_commands : GL.Buffer # DrawCommand
+    vertex_array : GL.VertexArray
+    program : GL.Program
+    transform : mat4
+    sprites_dirty = true
+
+    fn create ()
+        local self =
+            super-type.__typecall this-type
+        call
+            attach-shaders (deref self.program)
+                vertex = shaders.vertex-main
+                fragment = shaders.fragment-main
+                #debug = true
+        'reserve self.sprites MaxSpriteCount
+        'reserve self.commands MaxCommandCount
+        GL.NamedBufferData self.buffer_sprites
+            (sizeof Sprite) * MaxSpriteCount
+            \ null GL.STREAM_DRAW
+        GL.NamedBufferData self.buffer_commands
+            (sizeof DrawCommand) * MaxCommandCount
+            \ null GL.STREAM_DRAW
+        GL.UseProgram self.program
+        GL.BindBufferRange GL.SHADER_STORAGE_BUFFER
+            bindingof shaders.b_commands
+            self.buffer_commands
+            0
+            ((sizeof DrawCommand) * MaxCommandCount) as i64
+        GL.BindBufferRange GL.SHADER_STORAGE_BUFFER
+            bindingof shaders.b_sprites
+            self.buffer_sprites
+            0
+            ((sizeof Sprite) * MaxSpriteCount) as i64
+        GL.Uniform shaders.u_smp 0
+        GL.UseProgram 0
+        deref self
+
+    inline __typecall (cls)
+        create;
+
+    fn... register (self, rect : vec4)
+        'append self.sprites
+            Sprite
+                rect = rect
+    case (self, rect : ivec4, size : ivec2)
+        this-function self
+            (vec4 rect) / (vec2 size) . xyxy
+
+    fn invalidate (self)
+        self.sprites_dirty = true
+
+    fn... sprite
+    case (self, id : u32, origin : vec2, size : f32, angle : f32 = 0.0)
+        this-function self id origin (vec4 0 0 size size) angle
+    case (self, id : u32, origin : vec2, size : vec2, angle : f32 = 0.0)
+        this-function self id origin (vec4 0 0 size) angle
+    case (self, id : u32, origin : vec2, rect : vec4, angle : f32 = 0.0)
+        'append self.commands
+            DrawCommand
+                rect = rect
+                origin = origin
+                angle = angle
+                sprite = id
+
+    fn draw (self)
+        if (empty? self.commands)
+            return;
+        if self.sprites_dirty
+            if (not (empty? self.sprites))
+                self.sprites_dirty = false
+                GL.NamedBufferSubData self.buffer_sprites 0
+                    ((sizeof Sprite) * (countof self.sprites)) as i64
+                    & (self.sprites @ 0)
+        GL.NamedBufferSubData self.buffer_commands 0
+            ((sizeof DrawCommand) * (countof self.commands)) as i64
+            & (self.commands @ 0)
+        GL.UseProgram self.program
+        GL.Uniform shaders.u_transform self.transform
+        GL.BindVertexArray self.vertex_array
+        GL.DrawArraysInstanced GL.TRIANGLE_FAN 0 4
+            (countof self.commands) as i32
+        'clear self.commands
+
+    #fn... screen (self, size : vec2)
+        let aspect = (size.y / size.x)
+        scale self
+            vec3 aspect 1 1
+    #case (self, size : ivec2)
+        this-function self (vec2 size)
+
+do
+    let SpritePainter
+
+    locals;
+

          
M tukan/gl.sc +14 -0
@@ 834,6 834,11 @@ fn blit-framebuffer (source target size 
 inline vec-type-uniform-func (f)
     inline "glUniformVecType" (location value)
         f location (unpack value)
+@@ memo
+inline mat-type-uniform-func (f)
+    inline "glUniformMatrixType" (location value)
+        let ET = ((typeof value) . ElementType)
+        f location 1 GL_FALSE (bitcast &value (mutable @ET))
 
 fn get-uniform-dispatch-by-element-type (ET)
     if (ET < gsampler) `[GL.Uniform1i]

          
@@ 851,6 856,15 @@ fn get-uniform-dispatch-by-element-type 
         case uvec2 `[(vec-type-uniform-func GL.Uniform2ui)]
         case uvec3 `[(vec-type-uniform-func GL.Uniform3ui)]
         case uvec4 `[(vec-type-uniform-func GL.Uniform4ui)]
+        case mat2 `[(mat-type-uniform-func GL.UniformMatrix2fv)]
+        case mat2x3 `[(mat-type-uniform-func GL.UniformMatrix2x3fv)]
+        case mat2x4 `[(mat-type-uniform-func GL.UniformMatrix2x4fv)]
+        case mat3x2 `[(mat-type-uniform-func GL.UniformMatrix3x2fv)]
+        case mat3 `[(mat-type-uniform-func GL.UniformMatrix3fv)]
+        case mat3x4 `[(mat-type-uniform-func GL.UniformMatrix3x4fv)]
+        case mat4x2 `[(mat-type-uniform-func GL.UniformMatrix4x2fv)]
+        case mat4x3 `[(mat-type-uniform-func GL.UniformMatrix4x3fv)]
+        case mat4 `[(mat-type-uniform-func GL.UniformMatrix4fv)]
         default
             inline errmsg (location value)
                 static-error "unsupported uniform type"