Edit

kc3-lang/kc3/libkc3_window/sdl2/gl_sprite.c

Branch :

  • libkc3_window/sdl2/gl_sprite.c
  • /* kc3
     * Copyright 2022,2023,2024 kmx.io <contact@kmx.io>
     *
     * Permission is hereby granted to use this software granted the above
     * copyright notice and this permission paragraph are included in all
     * copies and substantial portions of this software.
     *
     * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
     * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
     * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
     * THIS SOFTWARE.
     */
    #include <libkc3/kc3.h>
    #include "gl_deprecated.h"
    #include "gl_object.h"
    #include "gl_sprite.h"
    #include "gl_triangle.h"
    
    void gl_sprite_clean (s_gl_sprite *sprite)
    {
      assert(sprite);
      str_clean(&sprite->path);
      str_clean(&sprite->real_path);
      glDeleteTextures(sprite->frame_count, sprite->texture);
      free(sprite->texture);
    }
    
    static bool png_info_to_gl_info (s32 png_color_type,
                                     s32 png_bit_depth,
                                     GLenum *gl_format,
                                     GLint  *gl_internal_format,
                                     GLenum *gl_type,
                                     u8 *components)
    {
      switch (png_bit_depth) {
      case 8:  *gl_type = GL_UNSIGNED_BYTE;  break;
      case 16: *gl_type = GL_UNSIGNED_SHORT; break;
      default: *gl_type = 0; return false;
      }
      switch (png_color_type) {
      case PNG_COLOR_TYPE_GRAY:
        *components = 1;
        *gl_format = GL_RED;
        switch (png_bit_depth) {
        case 8:  *gl_internal_format = GL_RED;  break;
        case 16: *gl_internal_format = GL_RED; break;
        default: *gl_internal_format = 0; return false;
        }
        break;
      case PNG_COLOR_TYPE_GRAY_ALPHA:
        *components = 2;
        *gl_format = GL_RG;
        switch (png_bit_depth) {
        case 8:  *gl_internal_format = GL_RG;  break;
        case 16: *gl_internal_format = GL_RG; break;
        default: *gl_internal_format = 0; return false;
        }
        break;
      case PNG_COLOR_TYPE_RGB:
        *components = 3;
        *gl_format = GL_RGB;
        switch (png_bit_depth) {
        case 8:  *gl_internal_format = GL_RGB;  break;
        case 16: *gl_internal_format = GL_RGB; break;
        default: *gl_internal_format = 0; return false;
        }
        break;
      case PNG_COLOR_TYPE_RGBA:
        *components = 4;
        *gl_format = GL_RGBA;
        switch (png_bit_depth) {
        case 8:  *gl_internal_format = GL_RGBA;  break;
        case 16: *gl_internal_format = GL_RGBA; break;
        default: *gl_internal_format = 0; return false;
        }
        break;
      default:
        *components = 0;
        *gl_format = 0;
        *gl_internal_format = 0;
        return false;
      }
      return true;
    }
    
    s_gl_sprite * gl_sprite_init (s_gl_sprite *sprite, const char *path,
                                  uw dim_x, uw dim_y, uw frame_count,
                                  f32 point_per_pixel)
    {
      u8 *data;
      FILE *fp;
      GLenum gl_format;
      GLint  gl_internal_format;
      GLenum gl_type;
      uw i;
      s32         png_bit_depth;
      s32         png_color_type;
      u8          png_components;
      png_bytep   png_data;
      u32         png_h;
      u8          png_header[8]; // maximum size is 8
      png_infop   png_info;
      uw          png_pixel_size;
      png_structp png_read;
      png_bytep  *png_row;
      u32         png_w;
      u8 *sprite_data;
      uw  sprite_stride;
      uw x;
      uw y;
      uw v;
      s_gl_sprite tmp = {0};
      assert(sprite);
      assert(path);
      assert(dim_x);
      assert(dim_y);
      assert(glGetError() == GL_NO_ERROR);
      tmp.frame_count = (frame_count > 0) ? frame_count :
        (dim_x * dim_y);
      str_init_copy_1(&tmp.path, path);
      if (! file_search(&tmp.path, sym_1("r"), &tmp.real_path)) {
        err_write_1("gl_sprite_init: file not found: ");
        err_puts(path);
        str_clean(&tmp.path);
        return NULL;
      }
      fp = fopen(tmp.real_path.ptr.pchar, "rb");
      if (! fp) {
        err_write_1("gl_sprite_init: fopen: ");
        err_puts(tmp.real_path.ptr.pchar);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      if (fread(png_header, 1, sizeof(png_header), fp) !=
          sizeof(png_header)) {
        err_write_1("gl_sprite_init: fread: ");
        err_puts(tmp.real_path.ptr.pchar);
        fclose(fp);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      if (png_sig_cmp(png_header, 0, sizeof(png_header))) {
        err_write_1("gl_sprite_init: not a png: ");
        err_puts(tmp.real_path.ptr.pchar);
        fclose(fp);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      png_read = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL,
    				    NULL);
      if (! png_read) {
        err_write_1("gl_sprite_init: png_create_read_struct: ");
        err_puts(tmp.real_path.ptr.pchar);
        fclose(fp);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      png_info = png_create_info_struct(png_read);
      if (! png_info) {
        err_write_1("gl_sprite_init: png_create_info_struct: ");
        err_puts(tmp.real_path.ptr.pchar);
        png_destroy_read_struct(&png_read, NULL, NULL);
        fclose(fp);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      gl_internal_format = 0;
      gl_format = 0;
      gl_type = 0;
      png_components = 0;
      if (setjmp(png_jmpbuf(png_read))) {
        png_destroy_read_struct(&png_read, &png_info, NULL);
        fclose(fp);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      png_set_sig_bytes(png_read, sizeof(png_header));
      png_init_io(png_read, fp);
      png_read_info(png_read, png_info);
      png_get_IHDR(png_read, png_info, &png_w, &png_h,
    	       &png_bit_depth, &png_color_type,
    	       NULL, NULL, NULL);
      if (png_color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_palette_to_rgb(png_read);
      if (! png_info_to_gl_info(png_color_type, png_bit_depth, &gl_format,
    			    &gl_internal_format, &gl_type,
    			    &png_components)) {
        if (! gl_format || ! png_components) {
          err_write_1("gl_sprite_init: unknown PNG color type ");
          err_inspect_s32(&png_color_type);
          err_write_1(": ");
          err_puts(tmp.real_path.ptr.pchar);
        }
        if (! gl_internal_format) {
          err_write_1("gl_sprite_init: unknown OpenGL internal format: ");
          err_puts(tmp.real_path.ptr.pchar);
        }
        if (! gl_type) {
          err_write_1("gl_sprite_init: unknown OpenGL type: ");
          err_puts(tmp.real_path.ptr.pchar);
        }
        png_destroy_read_struct(&png_read, &png_info, NULL);
        fclose(fp);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      png_pixel_size = (png_bit_depth / 8) * png_components;
      if (! png_pixel_size)
        png_error(png_read, "unknown png pixel size");
      if (png_h > PNG_SIZE_MAX / (png_w * png_pixel_size))
        png_error(png_read, "image_data buffer would be too large");
      png_data = png_malloc(png_read, png_h * png_w * png_pixel_size);
      png_row = png_malloc(png_read, png_h * sizeof(png_bytep));
      i = 0;
      while (i < png_h) {
        png_row[i] = png_data + i * png_w * png_pixel_size;
        i++;
      }
      if (png_bit_depth < 8)
        png_set_packing(png_read);
      png_read_image(png_read, png_row);
      png_destroy_read_struct(&png_read, &png_info, NULL);
      fclose(fp);
      tmp.total_w = png_w;
      tmp.total_h = png_h;
      tmp.dim_x = dim_x;
      tmp.dim_y = dim_y;
      tmp.pix_w = tmp.total_w / dim_x;
      tmp.pix_h = tmp.total_h / dim_y;
      tmp.texture = calloc(tmp.frame_count, sizeof(GLuint));
      if (! tmp.texture) {
        err_puts("gl_sprite_init: tmp.texture:"
                 " failed to allocate memory");
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      assert(glGetError() == GL_NO_ERROR);
      glGenTextures(tmp.frame_count, tmp.texture);
      GLenum gl_error = glGetError();
      if (gl_error != GL_NO_ERROR) {
        err_write_1("gl_sprite_init: ");
        err_inspect_str(&tmp.real_path);
        err_write_1(": glGenTextures: ");
        err_puts(gl_error_string(gl_error));
        free(tmp.texture);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      sprite_stride = tmp.pix_w * png_pixel_size;
      data = malloc(tmp.pix_h * sprite_stride);
      if (! data) {
        err_write_1("gl_sprite_init: failed to allocate memory: ");
        err_puts(tmp.real_path.ptr.pchar);
        free(tmp.texture);
        str_clean(&tmp.path);
        str_clean(&tmp.real_path);
        return NULL;
      }
      glActiveTexture(GL_TEXTURE0);
      i = 0;
      y = 0;
      while (i < tmp.frame_count && y < dim_y) {
        x = 0;
        while (i < tmp.frame_count && x < dim_x) {
          sprite_data = data + sprite_stride * tmp.pix_h;
          v = 0;
          while (v < tmp.pix_h) {
    	sprite_data -= sprite_stride;
    	memcpy(sprite_data,
    	       png_row[y * tmp.pix_h + v] + x * sprite_stride,
    	       sprite_stride);
    	v++;
          }
          glBindTexture(GL_TEXTURE_2D, tmp.texture[i]);
          gl_error = glGetError();
          if (gl_error != GL_NO_ERROR) {
    	err_write_1("gl_sprite_init: ");
            err_inspect_str(&tmp.real_path);
            err_write_1(": glBindTexture: ");
            err_puts(gl_error_string(gl_error));
    	return NULL;
          }
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
                          GL_CLAMP_TO_EDGE);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
                          GL_CLAMP_TO_EDGE);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                          GL_LINEAR_MIPMAP_LINEAR);
          glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
          gl_error = glGetError();
          if (gl_error != GL_NO_ERROR) {
    	err_write_1("gl_sprite_init: ");
            err_inspect_str(&tmp.real_path);
            err_write_1(": glTexParameteri: ");
            err_puts(gl_error_string(gl_error));
    	return NULL;
          }
          glTexImage2D(GL_TEXTURE_2D, 0, gl_format, tmp.pix_w, tmp.pix_h,
                       0, gl_format, gl_type, data);
          gl_error = glGetError();
          if (gl_error != GL_NO_ERROR) {
    	err_write_1("gl_sprite_init: ");
            err_inspect_str(&tmp.real_path);
            err_write_1(": glTexImage2D: ");
            err_puts(gl_error_string(gl_error));
    	return NULL;
          }
          glGenerateMipmap(GL_TEXTURE_2D);
          assert(glGetError() == GL_NO_ERROR);
          i++;
          x++;
        }
        y++;
      }
      glBindTexture(GL_TEXTURE_2D, 0);
      assert(glGetError() == GL_NO_ERROR);
      tmp.pt_w = tmp.pix_w * point_per_pixel;
      tmp.pt_h = tmp.pix_h * point_per_pixel;
      free(data);
      free(png_data);
      free(png_row);
      *sprite = tmp;  
      return sprite;
    }
    
    GLuint gl_sprite_texture (const s_gl_sprite *sprite, uw frame)
    {
      assert(sprite);
      assert(frame < sprite->frame_count);
      frame %= sprite->frame_count;
      return sprite->texture[frame];
    }