Merge: gamnit: new services and a lot of bug fixes and performance improvements
authorJean Privat <jean@pryen.org>
Wed, 29 Nov 2017 15:40:53 +0000 (10:40 -0500)
committerJean Privat <jean@pryen.org>
Wed, 29 Nov 2017 15:40:53 +0000 (10:40 -0500)
This PR groups small fixes to gamnit and related packages. It is a general cleanup in preparation for optimizations to the gamnit depth 3D API.

Intro a few new services:
* `ParticleSystem::clear` to remove all live particles.
* `CustomTexture` can be modified and reloaded in GPU memory after the first call to `load`.
* Don't apply dynamic resolution to UI sprites as they are usually lightweight and any change in resolution is easily noticable.

Optimizations:
* Matrix creation and manipulation, in both gamnit and the matrix package.
* Free pixel data from both Nit and Java memory after loading them from the assets folder into the GPU memory.
* Avoids using mallocs in `realtime` services.

Fixes:
* Fix the left and right anchors of the `UICamera`.
* Fix a constant used to ask for antialiasing in the `egl` package.
* Avoid long attribute names in Blinn-Phong shader in case their name is truncated or the attribute is optimized out.
* Fix pointer to the parent GPU texture name in subtextures.
* Improve a few API doc.

Pull-Request: #2586
Reviewed-by: Romain Chanoir <romain.chanoir@viacesi.fr>
Reviewed-by: Jean Privat <jean@pryen.org>

1  2 
contrib/asteronits/src/asteronits.nit
lib/gamnit/depth/depth.nit
lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_materials.nit
lib/gamnit/depth/shadow.nit
lib/gamnit/dynamic_resolution.nit
lib/gamnit/flat/flat_core.nit
lib/gamnit/textures.nit

@@@ -64,12 -64,14 +64,14 @@@ redef class Ap
        private var fx_explosion_ship = new Sound("sounds/explosion_ship.wav")
        private var fx_explosion_asteroids = new Sound("sounds/explosion_asteroids.wav")
  
 -      redef fun on_create
 +      redef fun create_scene
        do
                super
  
                # Move the camera to show all the world world in the screen range
                world_camera.reset_height(world.half_height * 2.0)
+               ui_camera.reset_height 720.0
        end
  
        # Main spritesheet with ships, asteroids and beams
@@@ -25,24 -25,19 +25,24 @@@ import shado
  
  redef class App
  
 -      redef fun on_create
 +      redef fun create_scene
        do
 -              super
 -
                # Move the camera back a bit
                world_camera.reset_height(10.0)
                world_camera.near = 0.1
  
 +              super
 +      end
 +
 +      redef fun create_gamnit
 +      do
 +              super
 +
                # Cull the invisible triangles in the back of the geometries
                glCullFace gl_BACK
  
                # Prepare programs
-               var programs = [versatile_program, normals_program, explosion_program, smoke_program, static_program, selection_program: GamnitProgram]
+               var programs = [blinn_phong_program, normals_program, explosion_program, smoke_program, static_program, selection_program: GamnitProgram]
                for program in programs do
                        program.compile_and_link
                        var gamnit_error = program.error
@@@ -70,7 -65,6 +70,7 @@@
                for actor in actors do
                        for leaf in actor.model.leaves do
                                leaf.material.draw(actor, leaf, app.world_camera)
 +                              assert glGetError == gl_NO_ERROR else print_error "Gamnit error on material {leaf.material.class_name}"
                        end
                end
                perfs["gamnit depth actors"].add frame_core_depth_clock.lapse
  
                # Toggle writing to the depth buffer for particles effects
                glDepthMask false
 -              for system in particle_systems do system.draw
 +              for system in particle_systems do
 +                      system.draw
 +                      assert glGetError == gl_NO_ERROR else print_error "OpenGL error in {system}"
 +              end
                glDepthMask true
                perfs["gamnit depth particles"].add frame_core_depth_clock.lapse
  
+               # Stop using the dynamic resolution before drawing UI sprites
+               frame_core_dynamic_resolution_after display
                frame_core_ui_sprites display
                perfs["gamnit depth ui_sprites"].add frame_core_depth_clock.lapse
  
-               frame_core_dynamic_resolution_after display
                # Debug, show the light point of view
                #frame_core_shadow_debug display
        end
@@@ -84,7 -84,7 +84,7 @@@ en
  
  # 3D model composed of `Mesh` and `Material`, loaded from the assets folder by default
  #
 -# Instances can be created at any time and must be loaded after or at the end of `on_create`.
 +# Instances can be created at any time and must be loaded after or at the end of `create_scene`.
  # If loading fails, the model is replaced by `placeholder_model`.
  #
  # ~~~
@@@ -185,7 -185,10 +185,10 @@@ en
  # ~~~
  class Mesh
  
-       # Vertices coordinates
+       # Number for vertices
+       fun n_vertices: Int do return vertices.length / 3
+       # Vertices relative coordinates, 3 floats per vertex
        var vertices = new Array[Float] is lazy, writable
  
        # Indices to draw triangles with `glDrawElements`
  
        private var indices_c = new CUInt16Array.from(indices) is lazy, writable
  
-       # Normals on each vertex
+       # Normals, 3 floats per vertex
        var normals = new Array[Float] is lazy, writable
  
-       # Coordinates on the texture per vertex
+       # Coordinates on the texture, 2 floats per vertex
        var texture_coords = new Array[Float] is lazy, writable
  
        # `GLDrawMode` used to display this mesh, defaults to `gl_TRIANGLES`
@@@ -49,16 -49,20 +49,20 @@@ class SmoothMateria
  
        redef fun draw(actor, model, camera)
        do
-               var program = app.versatile_program
+               var program = app.blinn_phong_program
                program.use
                program.mvp.uniform camera.mvp_matrix
  
                var mesh = model.mesh
  
                # Actor specs
+               glDisableVertexAttribArray program.translation.location
+               glDisableVertexAttribArray program.scale.location
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
-               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+               program.alpha.uniform actor.alpha
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
  
                # From mesh
                program.coord.array_enabled = true
                program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
  
                # Colors from the material
-               var a = actor.alpha
-               program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
-                                             ambient_color[2]*a, ambient_color[3]*a)
-               program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
-                                             diffuse_color[2]*a, diffuse_color[3]*a)
-               program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
-                                              specular_color[2]*a, specular_color[3]*a)
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1],
+                                             ambient_color[2], ambient_color[3])
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1],
+                                             diffuse_color[2], diffuse_color[3])
+               program.specular_color.uniform(specular_color[0], specular_color[1],
+                                              specular_color[2], specular_color[3])
  
-               setup_lights(actor, model, camera, program)
+               setup_lights(camera, program)
  
                # Execute draw
                if mesh.indices.is_empty then
                else
                        glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
 +
 +              assert glGetError == gl_NO_ERROR
        end
  
-       private fun setup_lights(actor: Actor, model: LeafModel, camera: Camera, program: BlinnPhongProgram)
+       private fun setup_lights(camera: Camera, program: BlinnPhongProgram)
        do
                # TODO use a list of lights
  
@@@ -158,7 -159,7 +161,7 @@@ class TexturedMateria
        do
                var mesh = model.mesh
  
-               var program = app.versatile_program
+               var program = app.blinn_phong_program
                program.use
  
                # One of the textures used, if any
                        program.use_map_bump.uniform false
                end
  
+               glDisableVertexAttribArray program.translation.location
+               glDisableVertexAttribArray program.scale.location
                program.mvp.uniform camera.mvp_matrix
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
+               program.alpha.uniform actor.alpha
  
                # If using a texture, set `texture_coords`
                program.tex_coord.array_enabled = sample_used_texture != null
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
  
-               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
  
-               var a = actor.alpha
-               program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
-                                             ambient_color[2]*a, ambient_color[3]*a)
-               program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
-                                             diffuse_color[2]*a, diffuse_color[3]*a)
-               program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
-                                              specular_color[2]*a, specular_color[3]*a)
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1],
+                                             ambient_color[2], ambient_color[3])
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1],
+                                             diffuse_color[2], diffuse_color[3])
+               program.specular_color.uniform(specular_color[0], specular_color[1],
+                                              specular_color[2], specular_color[3])
  
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
  
                # Light
-               setup_lights(actor, model, camera, program)
+               setup_lights(camera, program)
  
                # Camera
                program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
@@@ -292,7 -296,7 +298,7 @@@ class NormalsMateria
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
  
-               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
  
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
@@@ -314,10 -318,12 +320,12 @@@ class BlinnPhongProgra
                attribute vec4 coord;
  
                // Vertex translation
-               uniform vec4 translation;
+               attribute vec4 translation;
  
                // Vertex scaling
-               uniform float scale;
+               attribute float scale;
+               attribute float alpha;
  
                // Vertex coordinates on textures
                attribute vec2 tex_coord;
                // Camera model view projection matrix
                uniform mat4 mvp;
  
-               uniform mat4 rotation;
+               // Actor rotation
+               attribute vec4 rotation_row0;
+               attribute vec4 rotation_row1;
+               attribute vec4 rotation_row2;
+               attribute vec4 rotation_row3;
+               mat4 rotation()
+               {
+                       return mat4(rotation_row0, rotation_row1, rotation_row2, rotation_row3);
+               }
  
                // Lights config
                uniform lowp int light_kind;
                varying vec4 v_to_light;
                varying vec4 v_to_camera;
                varying vec4 v_depth_pos;
+               varying float v_alpha;
  
                void main()
                {
+                       mat4 rotation = rotation();
                        vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
                        gl_Position = pos * mvp;
                        v_depth_pos = (pos * light_mvp) * 0.5 + 0.5;
                                // Point light (and others?)
                                v_to_light = normalize(vec4(light_center, 1.0) - pos);
                        }
+                       v_alpha = alpha;
                }
                """ @ glsl_vertex_shader
  
                varying vec4 v_to_light;
                varying vec4 v_to_camera;
                varying vec4 v_depth_pos;
+               varying float v_alpha;
  
                // Colors
                uniform vec4 ambient_color;
                uniform lowp int light_kind;
                uniform bool use_shadows;
                uniform sampler2D depth_texture;
-               uniform float depth_texture_size;
-               uniform int depth_texture_taps;
+               uniform float depth_size;
+               uniform int depth_taps;
  
                // Shadow effect on the diffuse colors of the fragment at offset `x, y`
                float shadow_lookup(vec2 depth_coord, float x, float y) {
                        float tap_width = 1.0;
-                       float pixel_size = tap_width/depth_texture_size;
+                       float pixel_size = tap_width/depth_size;
  
                        vec2 offset = vec2(x * pixel_size * v_depth_pos.w,
                                           y * pixel_size * v_depth_pos.w);
  
                        vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
  
-                       float taps = float(depth_texture_taps);
+                       float taps = float(depth_taps);
                        float tap_step = 2.00/taps;
                        float sum = 0.0;
                        for (float x = -1.0; x <= 0.99; x += tap_step)
                        }
  
                        // Ambient light
-                       vec4 ambient = ambient_color;
+                       vec4 ambient = ambient_color * v_alpha;
                        if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
  
                        if (light_kind == 0) {
                                // No light, show diffuse and ambient
  
-                               vec4 diffuse = diffuse_color;
+                               vec4 diffuse = diffuse_color * v_alpha;
                                if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
  
                                gl_FragColor = ambient + diffuse;
                                        diffuse *= shadow();
                                }
  
-                               vec4 specular = s * specular_color;
+                               vec4 specular = s * specular_color * v_alpha;
                                if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
  
                                gl_FragColor = ambient + diffuse + specular;
        var depth_texture = uniforms["depth_texture"].as(UniformSampler2D) is lazy
  
        # Size, in pixels, of `depth_texture`
-       var depth_texture_size = uniforms["depth_texture_size"].as(UniformFloat) is lazy
+       var depth_texture_size = uniforms["depth_size"].as(UniformFloat) is lazy
  
        # Times to tap the `depth_texture`, square root (set to 3 for a total of 9 taps)
-       var depth_texture_taps = uniforms["depth_texture_taps"].as(UniformInt) is lazy
+       var depth_texture_taps = uniforms["depth_taps"].as(UniformInt) is lazy
  
        # Camera position
        var camera = uniforms["camera"].as(UniformVec3) is lazy
  
        # Translation applied to each vertex
-       var translation = uniforms["translation"].as(UniformVec4) is lazy
+       var translation = attributes["translation"].as(AttributeVec4) is lazy # TODO attribute
  
-       # Rotation matrix
-       var rotation = uniforms["rotation"].as(UniformMat4) is lazy
+       # Set `mat` at the uniform rotation matrix
+       fun rotation=(mat: Matrix)
+       do
+               var i = 0
+               for r in [rotation_row0, rotation_row1, rotation_row2, rotation_row3] do
+                       if r.is_active then
+                               glDisableVertexAttribArray r.location
+                               r.uniform(mat[0, i], mat[1, i], mat[2, i], mat[3, i])
+                       end
+                       i += 1
+               end
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+       end
+       # Rotation matrix, row0
+       var rotation_row0 = attributes["rotation_row0"].as(AttributeVec4) is lazy
+       # Rotation matrix, row 1
+       var rotation_row1 = attributes["rotation_row1"].as(AttributeVec4) is lazy
+       # Rotation matrix, row 2
+       var rotation_row2 = attributes["rotation_row2"].as(AttributeVec4) is lazy
+       # Rotation matrix, row 3
+       var rotation_row3 = attributes["rotation_row3"].as(AttributeVec4) is lazy
+       # Scaling per vertex
+       var scale = attributes["scale"].as(AttributeFloat) is lazy
  
        # Scaling per vertex
-       var scale = uniforms["scale"].as(UniformFloat) is lazy
+       var alpha = attributes["alpha"].as(AttributeFloat) is lazy
  
        # Camera model view projection matrix
        var mvp = uniforms["mvp"].as(UniformMat4) is lazy
@@@ -610,7 -657,7 +659,7 @@@ class NormalProgra
  end
  
  redef class App
-       private var versatile_program = new BlinnPhongProgram is lazy
+       private var blinn_phong_program = new BlinnPhongProgram is lazy
  
        private var normals_program = new NormalProgram is lazy
  end
@@@ -52,7 -52,7 +52,7 @@@ redef class Ap
  
        private var perf_clock_shadow = new Clock is lazy
  
 -      redef fun on_create
 +      redef fun create_gamnit
        do
                super
  
@@@ -84,8 -84,6 +84,6 @@@
                var light = app.light
                if not light isa LightCastingShadows then return
  
-               perf_clock_shadow.lapse
                # Make sure there's no errors pending
                assert glGetError == gl_NO_ERROR
  
                # Take down, bring back default values
                glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer)
                glColorMask(true, true, true, true)
-               perfs["gamnit shadows prep"].add perf_clock_shadow.lapse
        end
  
        # ---
@@@ -42,7 -42,7 +42,7 @@@ redef class Ap
        #
        # This value is applied to both X and Y, so it has an exponential effect on
        # the number of pixels.
-       var dynamic_resolution_ratio = 1.0
+       var dynamic_resolution_ratio = 1.0 is writable
  
        # Minimum dynamic screen resolution
        var min_dynamic_resolution_ratio = 0.0125 is writable
@@@ -54,7 -54,7 +54,7 @@@
  
        private var perf_clock_dynamic_resolution = new Clock is lazy
  
 -      redef fun on_create
 +      redef fun create_scene
        do
                super
  
@@@ -80,27 -80,27 +80,27 @@@ class Sprit
        var texture: Texture is writable(texture_direct=)
  
        # Texture drawn to screen
-       fun texture=(value: Texture)
+       fun texture=(texture: Texture)
        do
-               if isset _texture and value != texture then
+               if isset _texture and texture != self.texture then
                        needs_update
-                       if value.root != texture.root then needs_remap
+                       if texture.root != self.texture.root then needs_remap
                end
-               texture_direct = value
+               texture_direct = texture
        end
  
        # Center position of this sprite in world coordinates
        var center: Point3d[Float] is writable(center_direct=), noautoinit
  
        # Center position of this sprite in world coordinates
-       fun center=(value: Point3d[Float]) is autoinit do
-               if isset _center and value != center then
+       fun center=(center: Point3d[Float]) is autoinit do
+               if isset _center and center != self.center then
                        needs_update
-                       center.sprites_remove self
+                       self.center.sprites_remove self
                end
  
-               value.sprites_add self
-               center_direct = value
+               center.sprites_add self
+               center_direct = center
        end
  
        # Last animation set with `animate`
@@@ -417,15 -417,10 +417,15 @@@ redef class Ap
        # Second performance clock for smaller operations
        private var perf_clock_sprites = new Clock is lazy
  
 -      redef fun on_create
 +      redef fun create_gamnit
        do
                super
 +              create_flat
 +      end
  
 +      # Prepare the flat framework services
 +      fun create_flat
 +      do
                var display = display
                assert display != null
  
                        glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
                        glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
                end
 +
 +              sprites.reset
 +              ui_sprites.reset
        end
  
        redef fun on_stop
        do
 +              super
 +
                # Clean up
                simple_2d_program.delete
  
  
                # draw
                sprite_set.draw
 +
 +              assert glGetError == gl_NO_ERROR
        end
  
        # Draw world sprites from `sprites`
@@@ -975,7 -963,7 +975,7 @@@ class SpriteSe
                for sprite in sprites_to_remap do
  
                        # Skip if it was removed from this set after being modified
 -                      if sprite.context != self then continue
 +                      if sprite.sprite_set != self then continue
  
                        unmap_sprite sprite
                        map_sprite sprite
                for c in contexts_items do c.destroy
                contexts_map.clear
                contexts_items.clear
 +              sprites_to_remap.clear
 +      end
 +
 +      private fun reset
 +      do
 +              for sprite in self do
 +                      sprite.context = null
 +              end
 +
 +              for c in contexts_items do c.destroy
 +              contexts_map.clear
 +              contexts_items.clear
 +              sprites_to_remap.clear
 +
 +              for sprite in self do
 +                      map_sprite sprite
 +              end
        end
  end
  
  redef class GLfloatArray
        private fun fill_from_matrix(matrix: Matrix, dst_offset: nullable Int)
        do
-               dst_offset = dst_offset or else 0
+               dst_offset = dst_offset or else add_index
                var mat_len = matrix.width*matrix.height
                assert length >= mat_len + dst_offset
                native_array.fill_from_matrix_native(matrix.items, dst_offset, mat_len)
+               add_index += mat_len
        end
  end
  
diff --combined lib/gamnit/textures.nit
@@@ -20,7 -20,7 +20,7 @@@ import displa
  # Texture composed of pixels, loaded from the assets folder by default
  #
  # Most textures should be created with `App` (as attributes)
 -# for the method `on_create` to load them.
 +# for the method `create_scene` to load them.
  #
  # ~~~
  # import gamnit::flat
@@@ -29,9 -29,9 +29,9 @@@
  #     # Create the texture object, it will be loaded automatically
  #     var texture = new Texture("path/in/assets.png")
  #
 -#     redef fun on_create()
 +#     redef fun create_scene()
  #     do
 -#         # Let `on_create` load the texture
 +#         # Let `create_scene` load the texture
  #         super
  #
  #         # Use the texture
@@@ -41,7 -41,7 +41,7 @@@
  # end
  # ~~~
  #
 -# Otherwise, they can be loaded and error checked explicitly after `on_create`.
 +# Otherwise, they can be loaded and error checked explicitly after `create_scene`.
  #
  # ~~~nitish
  # var texture = new Texture("path/in/assets.png")
@@@ -154,10 -154,9 +154,9 @@@ class CustomTextur
        # The argument `color` should be an array of up to 4 floats (RGBA).
        # If `color` has less than 4 items, the missing items are replaced by 1.0.
        #
-       # Require: `not loaded and x < width.to_i and y < height.to_i`
+       # Require: `x < width.to_i and y < height.to_i`
        fun []=(x, y: Int, color: Array[Float])
        do
-               assert not loaded else print_error "{class_name}::[]= already loaded"
                assert x < width.to_i and y < height.to_i else print_error "{class_name}::[] out of bounds"
  
                # Simple conversion from [0.0..1.0] to [0..255]
  
                var offset = 4*(x + y*width.to_i)
                for i in [0..4[ do cpixels[offset+i] = bytes[i]
+               loaded = false
        end
  
        # Overwrite all pixels with `color`, return `self`
        #
        # The argument `color` should be an array of up to 4 floats (RGBA).
        # If `color` has less than 4 items, the missing items are replaced by 1.0.
-       #
-       # Require: `not loaded`
        fun fill(color: Array[Float]): SELF
        do
-               assert not loaded else print_error "{class_name}::fill already loaded"
                # Simple conversion from [0.0..1.0] to [0..255]
                var bytes = [for c in color do (c*255.0).round.to_i.clamp(0, 255).to_bytes.last]
                while bytes.length < 4 do bytes.add 255u8
                        end
                end
  
+               loaded = false
                return self
        end
  
        redef fun load(force)
        do
-               if loaded then return
+               force = force or else false
+               if loaded and not force then return
+               if force and glIsTexture(gl_texture) then
+                       # Was already loaded, free the previous GL name
+                       glDeleteTextures([gl_texture])
+               end
+               gl_texture = -1
  
                # Round down the desired dimension
                var width = width.to_i
  
                load_from_pixels(cpixels.native_array, width, height, gl_RGBA)
  
-               cpixels.destroy
                loaded = true
        end
  end
@@@ -338,7 -342,7 +342,7 @@@ abstract class Subtextur
        # Parent texture, from which this texture was created
        var parent: Texture
  
-       redef var root = parent.root is lateinit
+       redef fun root do return parent.root
  
        redef fun load(force) do root.load(force)
  end
@@@ -392,7 -396,7 +396,7 @@@ class TextureSe
  end
  
  redef class Pointer
-       # Multiply RBG values by their alpha value
+       # Multiply RGB values by their alpha value
        private fun premultiply_alpha(width, height: Int) `{
                uint8_t *bytes = (uint8_t *)self;
                int x, y, i = 0;