Untitled

 avatar
unknown
plain_text
a month ago
18 kB
9
Indexable
#include "game.h"
#include "lib/array.h"
#include "lib/cglm/cglm.h"
#include "lib/common.h"
#include "lib/memory.h"
#include "lib/stb/stb_image.h"
#include "lib/typedefs.h"
#include "platform.h"
#include "renderer.h"

global ArenaAllocator g_temp_allocator;
global ArenaAllocator g_allocator;

void *global_alloc(size_t size) { return arena_alloc(&g_temp_allocator, size); }
void *global_realloc(void *ptr, size_t size) {
  return arena_realloc(&g_temp_allocator, ptr, size);
}

typedef struct {
  RendererBatchId batch_id;
  mat4 model_matrix;
} EntityVisuals;

typedef struct {
  RendererBatchId batch_id;
  BatchInstanceData_Slice instances;
} EntityDrawBatch;
slice_define(EntityDrawBatch);

typedef struct {
  struct {
    union {
      struct {
        GameButton left;
        GameButton right;
        GameButton up;
        GameButton down;
      };
      GameButton buttons[4];
    };
  } input;

  EntityDrawBatch_Slice batches;

  vec3 camera_pos;
  mat4 viewMatrix;
  mat4 projectionMatrix;
  mat4 viewProjectionMatrix;
} GameState;

void update_input_button(GameButton *btn, GameInputEvent event) {
  if (event.type == EVENT_KEYUP) {
    btn->released_this_frame = btn->is_pressed;
    btn->pressed_this_frame = false;
    btn->is_pressed = false;
  } else if (event.type == EVENT_KEYDOWN) {
    btn->pressed_this_frame = !btn->is_pressed;
    btn->released_this_frame = false;
    btn->is_pressed = true;
  }
}

export void game_init(GameMemory *memory) {
  // todo: we could just have the platform pass the max memory size
  g_temp_allocator =
      arena_from_buffer(memory->temp_buffer, sizeof(memory->temp_buffer));
  assert(g_temp_allocator.buffer);

  u64 heap_start = cast(u64) platform_get_heap_base();
  u64 permanent_memory_start = heap_start + sizeof(GameMemory);
  u64 permanent_memory_size = WASM_MEMORY_SIZE - permanent_memory_start;
  memory->permanent_memory = cast(uint8 *) permanent_memory_start;

  g_allocator =
      arena_from_buffer(memory->permanent_memory, permanent_memory_size);
  assert(g_allocator.buffer);
}

export void game_update_and_render(GameMemory *memory) {
  UNUSED(memory);
  local_persist PlatformReadFileOp load_sphere_op = -1;
  local_persist PlatformReadFileOp load_tex_op = -1;
  local_persist PlatformReadFileOp load_cube_op = -1;
  local_persist Model3DData *sphere_mesh = NULL;
  local_persist Model3DData *cube_mesh = NULL;
  local_persist Image *tex_data = NULL;

  local_persist GameState *game_state;

  if (!game_state) {
    game_state = GAME_ALLOC(GameState);
    assert(game_state);
    glm_mat4_identity(game_state->viewMatrix);
    glm_mat4_identity(game_state->projectionMatrix);
    glm_mat4_identity(game_state->viewProjectionMatrix);
    glm_vec3(cast(vec3){0, -0.6, -1.25}, game_state->camera_pos);

    game_state->batches = cast(EntityDrawBatch_Slice){
        .cap = 128, .len = 0, .items = GAME_ALLOC_ARRAY(EntityDrawBatch, 128)};
    assert(game_state->batches.items);
  }

  if (load_sphere_op < 0) {
    load_sphere_op = platform_start_read_file("unichan.hmobj");
  }
  if (load_cube_op < 0) {
    load_cube_op = platform_start_read_file("unichan.hmobj");
  }
  if (load_tex_op < 0) {
    load_tex_op = platform_start_read_file("unichan.png");
  }

  if (platform_check_read_file(load_sphere_op) == FREADSTATE_COMPLETED) {
    PlatformFileData data = {};
    if (platform_get_file_data(load_sphere_op, &data, &g_temp_allocator)) {
      sphere_mesh = load_model(data.buffer, data.buffer_len, &g_temp_allocator);
    }
  }

  if (platform_check_read_file(load_cube_op) == FREADSTATE_COMPLETED) {
    PlatformFileData data = {};
    if (platform_get_file_data(load_cube_op, &data, &g_temp_allocator)) {
      cube_mesh = load_model(data.buffer, data.buffer_len, &g_temp_allocator);
    }
  }

  if (platform_check_read_file(load_tex_op) == FREADSTATE_COMPLETED) {
    PlatformFileData data = {};
    if (platform_get_file_data(load_tex_op, &data, &g_temp_allocator)) {
      LOG_INFO("Loading png");
      // Decode the PNG texture using lodepng_decode32
      int x, y, n;
      u8 *decoded_data =
          stbi_load_from_memory(data.buffer, data.buffer_len, &x, &y, &n, 0);

      LOG_INFO("end load png");
      if (decoded_data) {
        // Allocate and populate the tex_data structure
        tex_data = ARENA_ALLOC(&g_temp_allocator, Image);
        tex_data->width = x;
        tex_data->height = y;
        tex_data->byte_len = data.buffer_len;
        tex_data->data = (uint8 *)decoded_data;
      }
    }
  }

  if (sphere_mesh && tex_data && cube_mesh) {

    for (u32 i = 0; i < sphere_mesh->num_meshes; i++) {
      MeshData *mesh = &sphere_mesh->meshes[i];
      RendererBatchId sphere_batch_id = renderer_create_batch(mesh, tex_data);

      EntityDrawBatch sphere_batch = cast(EntityDrawBatch){
          .batch_id = sphere_batch_id,
          .instances = {.cap = 128,
                        .len = 0,
                        .items = GAME_ALLOC_ARRAY(BatchInstanceData, 128)}};
      slice_append(game_state->batches, sphere_batch);
    }
    cube_mesh = NULL;
    sphere_mesh = NULL;
    tex_data = NULL;
  }

  // input stuff
  GameInputEvents *input_events = &memory->input_events;
  for (u32 i = 0; i < input_events->len; i++) {
    GameInputEvent e = input_events->events[i];
    if (e.key.type == KEY_D) {
      update_input_button(&game_state->input.right, e);
    } else if (e.key.type == KEY_A) {
      update_input_button(&game_state->input.left, e);
    } else if (e.key.type == KEY_W) {
      update_input_button(&game_state->input.up, e);
    } else if (e.key.type == KEY_S) {
      update_input_button(&game_state->input.down, e);
    }
  }

  f32 dt = memory->time.dt;
  f32 cam_speed = 2;

  if (game_state->input.left.is_pressed) {
    game_state->camera_pos[0] += cam_speed * dt;
  }
  if (game_state->input.right.is_pressed) {
    game_state->camera_pos[0] -= cam_speed * dt;
  }
  if (game_state->input.up.is_pressed) {
    game_state->camera_pos[1] -= cam_speed * dt;
  }
  if (game_state->input.down.is_pressed) {
    game_state->camera_pos[1] += cam_speed * dt;
  }

  // render
  glm_mat4_identity(game_state->viewMatrix);

  glm_translate(game_state->viewMatrix, game_state->camera_pos);

  f32 aspect = cast(f32) memory->canvas.width / memory->canvas.height;
  f32 fov = 60;

  glm_perspective(glm_rad(fov), aspect, 0.1, 100, game_state->projectionMatrix);
  glm_mat4_mul(game_state->projectionMatrix, game_state->viewMatrix,
               game_state->viewProjectionMatrix);

  renderer_set_uniforms(game_state->viewMatrix, game_state->projectionMatrix,
                        game_state->viewProjectionMatrix);

  // batch stuff
  for (u32 i = 0; i < game_state->batches.len; i++) {
    EntityDrawBatch *batch = &game_state->batches.items[i];

    batch->instances.len = 0;

    BatchInstanceData m1 = {};
    // BatchInstanceData m2 = {};
    // BatchInstanceData m3 = {};
    glm_mat4_identity(m1.model_matrix);
    // glm_mat4_identity(m2.model_matrix);
    // glm_mat4_identity(m3.model_matrix);

    // glm_translate_x(m1.model_matrix, 1.5 * (i + 1));
    // glm_rotate_x(m1.model_matrix, glm_rad(30), m1.model_matrix);
    // glm_rotate_y(m1.model_matrix, glm_rad(-45), m1.model_matrix);
    // glm_translate_x(m2.model_matrix, -1.5 * (i + 1));
    // glm_translate_y(m3.model_matrix, 1 * (i + 1));

    slice_append(batch->instances, m1);
    // slice_append(batch->instances, m2);
    // slice_append(batch->instances, m3);

    batch_set_instance_data(batch->batch_id, batch->instances.items,
                            batch->instances.len);
  }

  // clean input
  for (u32 i = 0; i < ARRAY_SIZE(game_state->input.buttons); i++) {
    game_state->input.buttons[i].released_this_frame = false;
    game_state->input.buttons[i].pressed_this_frame = false;
  }

  LOG_INFO("\nTemp Memory %/% mb\nMain Memory %/%",
           FMT_UINT(BYTES_TO_MB(g_temp_allocator.offset)),
           FMT_UINT(BYTES_TO_MB(g_temp_allocator.capacity)),
           FMT_UINT(BYTES_TO_MB(g_allocator.offset)),
           FMT_UINT(BYTES_TO_MB(g_allocator.capacity)));

  arena_reset(&g_temp_allocator);
}


renderer.c (simplified)
#include "renderer.h"
#include "lib/memory.h"
#include "lib/typedefs.h"
#include <string.h>

// todo: gotta handle cross platform compilation
#if defined(__wasm__)
extern RendererBatchId _renderer_create_batch(f32 *verts, u32 len_verts,
                                              u32 *indices, u32 len_indices,
                                              u8 *texture, u32 len_texture,
                                              u32 width_texture,
                                              u32 height_texture);
extern bool32 _renderer_update_batch(RendererBatchId batch_id, f32 *instances,
                                     u32 float_count);
extern void _renderer_set_uniforms(f32 *view_matrix, f32 *projection_matrix,
                                   f32 *view_projection_matrix);

RendererBatchId renderer_create_batch(const MeshData *mesh,
                                      Image *texture) {
  return _renderer_create_batch(mesh->vertex_buffer, mesh->len_vertex_buffer,
                                mesh->indices, mesh->len_indices, texture->data,
                                texture->byte_len, texture->width,
                                texture->height);
}

bool32 batch_set_instance_data(RendererBatchId batch_id,
                               BatchInstanceData *instances, u32 len) {
  u32 float_count = len * sizeof(BatchInstanceData) / sizeof(float32);
  return _renderer_update_batch(batch_id, (f32 *)instances, float_count);
}

void renderer_set_uniforms(mat4 view_matrix, mat4 projection_matrix,
                           mat4 view_projection_matrix) {
  _renderer_set_uniforms(cast(f32 *) view_matrix, cast(f32 *) projection_matrix,
                         cast(f32 *) view_projection_matrix);
}
#endif

typedef struct {
  u32 len;
  const uint8 *bytes;
  u32 cur_offset;
} BinaryReader;

bool32 read_u32(BinaryReader *reader, u32 *v) {
  u32 end_offset = reader->cur_offset + sizeof(u32);
  bool32 within_range = end_offset <= reader->len;
  assert(within_range);
  if (!within_range) {
    return false;
  }

  memcpy(v, &reader->bytes[reader->cur_offset], sizeof(u32));

  reader->cur_offset = end_offset;

  return true;
}

bool32 read_f32_array(BinaryReader *reader, f32 *arr, u32 len) {
  u32 end_offset = reader->cur_offset + sizeof(f32) * len;
  bool32 within_range = end_offset <= reader->len;
  assert(within_range);
  if (!within_range) {
    return false;
  }

  memcpy(arr, &reader->bytes[reader->cur_offset], len * sizeof(f32));
  reader->cur_offset = end_offset;
  return true;
}

bool32 read_u32_array(BinaryReader *reader, u32 *arr, u32 len) {
  u32 end_offset = reader->cur_offset + sizeof(u32) * len;
  bool32 within_range = end_offset <= reader->len;
  assert(within_range);
  if (!within_range) {
    return false;
  }

  memcpy(arr, &reader->bytes[reader->cur_offset], len * sizeof(u32));
  reader->cur_offset = end_offset;
  return true;
}

Model3DData *load_model(const uint8 *binary_data, u32 data_len,
                        ArenaAllocator *allocator) {
  if (binary_data == NULL || data_len == 0 || allocator == NULL) {
    return NULL; // Invalid input
  }

  BinaryReader reader = {
      .bytes = binary_data, .len = data_len, .cur_offset = 0};
  Model3DData *model = ARENA_ALLOC(allocator, Model3DData);
  if (model == NULL) {
    return NULL;
  }

  read_u32(&reader, &model->version);
  read_u32(&reader, &model->num_meshes);

  model->meshes = ARENA_ALLOC_ARRAY(allocator, MeshData, model->num_meshes);
  if (model->meshes == NULL) {
    return NULL;
  }

  // Read mesh data
  for (u32 i = 0; i < model->num_meshes; i++) {
    MeshData *mesh = &model->meshes[i];

    read_u32(&reader, &mesh->len_vertices);
    read_u32(&reader, &mesh->len_vertex_buffer);

    mesh->vertex_buffer =
        ARENA_ALLOC_ARRAY(allocator, f32, mesh->len_vertex_buffer);
    if (mesh->vertex_buffer == NULL) {
      return NULL;
    }

    read_f32_array(&reader, mesh->vertex_buffer, mesh->len_vertex_buffer);

    read_u32(&reader, &mesh->len_indices);

    mesh->indices = ARENA_ALLOC_ARRAY(allocator, u32, mesh->len_indices);
    if (mesh->indices == NULL) {
      return NULL;
    }

    read_u32_array(&reader, mesh->indices, mesh->len_indices);
  }

  assert(reader.cur_offset == reader.len);

  return model;
}

game.ts (simplified)

  function _renderer_create_batch(
    vertPtr: number,
    vertLen: number,
    indexPtr: number,
    indexLen: number,
    texPtr: number,
    texLen: number,
    texWidth: number,
    texHeight: number,
  ) {
    const verticesView = new Float32Array(
      wasmMemory.memory.buffer,
      vertPtr,
      vertLen,
    );
    const indicesView = new Uint32Array(
      wasmMemory.memory.buffer,
      indexPtr,
      indexLen,
    );
    const mesh: Mesh = {
      vertices: verticesView,
      indices: indicesView,
    };

    const batch = StandardMaterialBatch.new(
      renderer.gl,
      renderer.standardPipeline.program,
      mesh,
    );

    texLen = texWidth * texHeight * 4;
    const texData = new Uint8Array(wasmMemory.memory.buffer, texPtr, texLen);
    batch.setTextureFromRGBA(renderer.gl, texData, texWidth, texHeight);
    renderer.standardPipeline.batches.push(batch);

    return renderer.standardPipeline.batches.length;
  }

  function _renderer_update_batch(
    batchId: number,
    instanceDataPtr: number,
    instanceDataLen: number,
  ) {
    const batch = renderer.standardPipeline.batches[batchId - 1];
    if (!batch) {
      return false;
    }
    const instanceData = wasmMemory.loadF32Array(
      instanceDataPtr,
      instanceDataLen,
    );
    batch.updateInstanceData(renderer.gl, instanceData);
  }

  function _renderer_set_uniforms(
    viewMatrixPtr: number,
    projectionMatrixPtr: number,
    viewProjectionMatrixPtr: number,
  ) {
    renderer.viewMatrix = wasmMemory.loadF32Array(viewMatrixPtr, 16);
    renderer.projectionMatrix = wasmMemory.loadF32Array(
      projectionMatrixPtr,
      16,
    );
    renderer.viewProjectionMatrix = wasmMemory.loadF32Array(
      viewProjectionMatrixPtr,
      16,
    );
  }

  const wasmModule = await WebAssembly.instantiate(
    await response.arrayBuffer(),
    {
      env: {
        memory,
        _platform_log_info,
        _platform_log_warn,
        _platform_log_error,
        _platform_start_read_file,
        _platform_check_read_file,
        _platform_get_file_size,
        _platform_get_file_data,
        _renderer_create_batch,
        _renderer_update_batch,
        _renderer_set_uniforms,
      },
    },
  );

renderer.ts (simplified)

export class Renderer {
  gl: WebGL2RenderingContext;
  canvas: HTMLCanvasElement;

  viewMatrix: mat4;
  projectionMatrix: mat4;
  viewProjectionMatrix: mat4;

  //hdri
  frameBuffer: FrameBuffer;

  //frame buffer quad
  quadVAO: WebGLVertexArrayObject;
  quadVBO: WebGLBuffer;
  presentProgram: WebGLProgram;

  standardPipeline: StandardMaterialPipeline;

  private resizeOnNextDraw: boolean = false;

  static new(canvas: HTMLCanvasElement) {
    //@ts-ignore
    const renderer = new Renderer();

    const gl = canvas.getContext("webgl2", {
      antialias: true,
      powerPreference: "high-performance",
    });
    if (!gl) {
      throw new Error("WebGL2 not supported");
    }

    //make sure canvas is the right size before proceeding
    const dpi = window.devicePixelRatio || 1;
    canvas.width = window.innerWidth * dpi;
    canvas.height = window.innerHeight * dpi;

    renderer.frameBuffer = FrameBuffer.new(gl);

    //setup present shader
    {
      const quadVAO = gl.createVertexArray();
      gl.bindVertexArray(quadVAO);
      const quadVBO = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, quadVBO);

      // prettier-ignore
      const quadVertices = new Float32Array([
    -1, -1,  0, 0,
     1, -1,  1, 0,
    -1,  1,  0, 1,
     1,  1,  1, 1
    ]);
      gl.bufferData(gl.ARRAY_BUFFER, quadVertices, gl.STATIC_DRAW);

      //pos
      gl.enableVertexAttribArray(0);
      gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 16, 0);
      //uv
      gl.enableVertexAttribArray(1);
      gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 16, 8);

      gl.bindVertexArray(null);

      const presentProgram = createProgram(
        gl,
        vertexShaderSource,
        fragmentShaderSource,
      );

      renderer.quadVAO = quadVAO;
      renderer.quadVBO = quadVBO;
      renderer.presentProgram = presentProgram;
    }

    renderer.gl = gl;
    renderer.canvas = canvas;

    renderer.resize();

    return renderer;
  }

  resize() {
    this.resizeOnNextDraw = true;
  }

  render() {
    const { gl, viewProjectionMatrix, standardPipeline, frameBuffer } = this;

    if (gl.isContextLost()) {
      console.error("Context lost");
      return;
    }

    if (this.resizeOnNextDraw) {
      const { canvas, gl } = this;
      const dpi = window.devicePixelRatio || 1;
      canvas.width = window.innerWidth * dpi;
      canvas.height = window.innerHeight * dpi;

      frameBuffer.resize(gl);

      this.resizeOnNextDraw = false;
    }

    //render to the frame buffer
    frameBuffer.bind(gl);

    gl.clearColor(0.1, 0.1, 0.1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.enable(gl.DEPTH_TEST);

    standardPipeline.render(gl, viewProjectionMatrix);

    frameBuffer.unbind(gl);

    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    const { presentProgram, quadVAO } = this;
    gl.useProgram(presentProgram);
    gl.uniform1i(gl.getUniformLocation(presentProgram, "hdrTexture"), 0);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, frameBuffer.frameTexture);

    gl.bindVertexArray(quadVAO);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    gl.bindVertexArray(null);
    gl.bindTexture(gl.TEXTURE_2D, null);
  }
}

standard_pipeline.ts (simplified)

//batch rendering code
  render(gl: WebGL2RenderingContext) {
    const {
      vao,
      texture,
      instanceCount,
      textureUniformLoc,
      indexCount,
      glIndexType,
    } = this;
    gl.bindVertexArray(vao);

    gl.uniform1i(textureUniformLoc, 0);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, texture);

    gl.drawElementsInstanced(
      gl.TRIANGLES,
      indexCount,
      glIndexType,
      0,
      instanceCount,
    );
    gl.bindVertexArray(null);
    gl.bindTexture(gl.TEXTURE_2D, null);
  }
Editor is loading...
Leave a Comment