Untitled
unknown
plain_text
7 months ago
18 kB
20
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