System info & network

This commit is contained in:
2023-09-26 19:40:16 +02:00
commit 504ba77654
89 changed files with 39577 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
server_monitor
cache_build

Binary file not shown.

BIN
assets/fonts/DejaVuSans.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

93
build.sh Executable file
View File

@@ -0,0 +1,93 @@
#!/bin/bash
start_time=$(date +%s%N) # Performance timer
ProgramName="server_monitor"
# User defines
UserDefines="-DDEBUG"
# Compiler
CXX="g++"
CompilerFlags="-std=c++11"
CompilerFlags+=" -g"
#CompilerFlags+=" -fsanitize=address"
CompilerFlags+=" -fno-rtti -fno-exceptions"
CompilerFlags+=" -Wall -Wno-unused-variable -Wno-unused-but-set-variable -Wno-sign-compare -Wno-unused-value -Wno-unused-function -Werror=return-type -Wno-narrowing"
# Definitions and configuration
CompilerFlags+=" -DPLATFORM_LINUX=1"
#CompilerFlags+=" -DGUI_PERFORMANCE_REPORT"
CompilerFlags+=" -DSWAP_INTERVAL=1"
# Source files
SourceFiles="code/*.cpp"
SourceFiles+=" code/**/*.cpp"
# X11
CompilerFlags+=" -lX11 -lXi"
# OpenGL
CompilerFlags+=" -lGL -ldl"
CompilerFlags+=" -DGL_GLEXT_PROTOTYPES"
CompilerFlags+=" -DGLX_GLXEXT_PROTOTYPES"
# Pulseaudio
CompilerFlags+=" -lpulse"
# NetworkManager
CompilerFlags+=" "$(pkg-config --libs --cflags libnm)
# External libs
CompilerFlags+=" -Iexternal"
ExternalFiles="external/*.cpp"
ExternalObjects=""
for f in $ExternalFiles;
do
ObjName="cache_build/${f#"external/"}"
ObjName=${ObjName%.cpp}.o
ExternalObjects+=" ${ObjName}"
done
SourceFiles+="${ExternalObjects}"
# Cache external libs building. They are big and take a lot of time to compile
build_external=false
for obj in $ExternalObjects;
do
if [ ! -e $obj ]
then
build_external=true
fi
done
if [ $build_external == true ]
then
echo "Building external libraries cache..."
mkdir -p cache_build
for f in $ExternalFiles;
do
ObjName="cache_build/${f#"external/"}"
ObjName=${ObjName%.cpp}.o
echo $ObjName
$CXX $CompilerFlags $UserDefines -O2 $f -c -o $ObjName
done
fi
echo "Compiling..."
$CXX $CompilerFlags $UserDefines $SourceFiles -o $ProgramName
compiled=$?
if [ $compiled != 0 ]
then
exit $compiled
fi
echo "Done!"
end_time=$(date +%s%N) # Performance timer
duration_nanoseconds=$((end_time - start_time)) # Performance timer
duration_string="$((duration_nanoseconds/1000000/1000)).$((duration_nanoseconds/1000000%1000))"
echo #newline
echo "Duration: "$duration_string"s"

705
code/assets.cpp Normal file
View File

@@ -0,0 +1,705 @@
#include "assets.h"
#include "file_formats/wavefront.h"
#include "platform.h"
#include <stdlib.h>
#include <string.h>
#include "stb_image.h"
#include "stb_vorbis.h"
#include <limits.h>
#include "debug/logger.h"
#include "render/render.h"
#include "cgltf.h"
Buffer read_file_into_buffer(const char *filename);
int wf_face_index_comp(const void *_a, const void *_b)
{
wf_face_index *a = (wf_face_index *)_a;
wf_face_index *b = (wf_face_index *)_b;
if(a->vertex < b->vertex) return -1;
if(a->vertex > b->vertex) return +1;
if(a->texture < b->texture) return -1;
if(a->texture > b->texture) return +1;
if(a->normal < b->normal) return -1;
if(a->normal > b->normal) return +1;
return 0;
}
bool assets_load_object_obj(asset_manager *am, const char *filename, r_object *result)
{
wf_obj_file obj_file;
wf_mtl_file mtl_file;
bool has_mtl;
char path[512]; // @Buf: Files with paths longer than 512 bytes will fail to load
p_file file;
u64 size;
Buffer content;
bool success;
// Load obj file
strcpy(path, "assets/");
strncat(path, filename, 512 - strlen(path));
p_file_init(&file, path);
size = p_file_size(&file);
content.data = (u8*)p_alloc(size);
content.size = 0;
p_file_read(&file, &content, size);
p_file_deinit(&file);
success = wf_parse_obj((char*)content.data, content.size, filename, &obj_file);
p_free(content.data);
if(!success)
return false;
has_mtl = false;
// Load mtl file (optional)
if(obj_file.mtllib_names_count > 0)
{
has_mtl = true;
char *mtl_name = obj_file.mtllib_names[0];
strcpy(path, "assets/");
strncat(path, mtl_name, 512 - strlen(path));
p_file_init(&file, path);
size = p_file_size(&file);
content.data = (u8*)p_alloc(size);
content.size = 0;
p_file_read(&file, &content, size);
p_file_deinit(&file);
success = wf_parse_mtl((char*)content.data, content.size, mtl_name, &mtl_file);
p_free(content.data);
if(!success)
return false;
}
// We take only the first object
if(obj_file.objects_count == 0)
{
LOG(LOG_WARNING, "Asset: cannot load model. File does not contain any objects.");
return false;
}
wf_object *object = obj_file.objects;
// Setup model
r_mesh *res_model = assets_new_models(am, 1);
memset(res_model, 0, sizeof(r_mesh));
// Actual conversion
{
wf_face_index *unique_indices;
u32 unique_indices_count;
unique_indices_count = object->faces_count * 3;
unique_indices = (wf_face_index *)p_alloc(sizeof(wf_face_index) * unique_indices_count);
memcpy(unique_indices, object->faces, sizeof(wf_face_index) * unique_indices_count);
qsort(unique_indices, unique_indices_count, sizeof(wf_face_index), wf_face_index_comp);
u32 removed = 0;
for(u32 i = 1; i < unique_indices_count; i++)
{
wf_face_index *prev = unique_indices + i - 1;
wf_face_index *curr = unique_indices + i;
if(wf_face_index_comp(prev, curr) == 0) // Remove duplicate
removed++;
unique_indices[i - removed] = unique_indices[i];
}
unique_indices_count -= removed;
res_model->vertices_count = unique_indices_count;
res_model->vertices = (v3*)p_alloc(sizeof(v3) * unique_indices_count);
if(object->texture_coords)
res_model->uvs = (v2*)p_alloc(sizeof(v2) * unique_indices_count);
else
res_model->uvs = NULL;
res_model->normals = (v3*)p_alloc(sizeof(v3) * unique_indices_count);
for(u32 i = 0; i < unique_indices_count; i++)
{
wf_index vertex_i = (unique_indices[i].vertex > 0 ? unique_indices[i].vertex - 1 : 0);
wf_index texture_coord_i = (unique_indices[i].texture > 0 ? unique_indices[i].texture - 1 : 0);
wf_index normal_i = (unique_indices[i].normal > 0 ? unique_indices[i].normal - 1 : 0);
res_model->vertices[i] = object->vertices [vertex_i].xyz / object->vertices[vertex_i].w;
if(object->texture_coords)
res_model->uvs[i] = object->texture_coords[texture_coord_i].uv;
res_model->normals[i] = object->normals[normal_i];
}
res_model->indices_count = object->faces_count * 3;
res_model->indices = (u32*)p_alloc(sizeof(u32) * object->faces_count * 3);
for(u32 f = 0; f < object->faces_count; f++)
{
wf_face *curr_face = object->faces + f;
for(u32 fi = 0; fi < 3; fi++)
{
wf_face_index *curr_index = curr_face->indices + fi;
wf_face_index *found_ptr = (wf_face_index *)bsearch(curr_index, unique_indices, unique_indices_count, sizeof(wf_face_index), wf_face_index_comp);
res_model->indices[3 * f + fi] = (found_ptr - unique_indices);
}
}
p_free(unique_indices);
}
// Setup material
r_material *res_material = assets_new_materials(am, 1);
memset(res_material, 0, sizeof(r_material));
res_material->shader = &r_render_state.shader_pbr;
// Load textures
if(object->material_name != NULL && has_mtl)
{
// @Feature: Load multiple materials
wf_material *material = NULL;
for(wf_size i = 0; i < mtl_file.materials_count; i++)
{
if(strcmp(object->material_name, mtl_file.materials[i].name) == 0)
{
material = mtl_file.materials + i;
break;
}
}
if(material)
{
// Load diffuse texture
if(material->map_Kd && strcmp(material->map_Kd, "") != 0)
{
strcpy(path, "assets/");
strncat(path, material->map_Kd, 512 - strlen(path));
stbi_set_flip_vertically_on_load(true);
v2s size; s32 channels;
u8 *data = stbi_load(path, &size.x, &size.y, &channels, 4);
r_texture *texture = assets_new_textures(am, 1);
*texture = r_texture_create(data, size, R_TEXTURE_SRGB);
res_material->albedo_texture = texture;
res_material->albedo_factor = v4{1,1,1,1};
}
// Load emissive texture
if(material->map_Ke && strcmp(material->map_Ke, "") != 0)
{
strcpy(path, "assets/");
strncat(path, material->map_Ke, 512 - strlen(path));
stbi_set_flip_vertically_on_load(true);
v2s size; s32 channels;
u8 *data = stbi_load(path, &size.x, &size.y, &channels, 4);
r_texture *texture = assets_new_textures(am, 1);
*texture = r_texture_create(data, size, R_TEXTURE_SRGB);
res_material->emissive_texture = texture;
res_material->emissive_factor = v4{1,1,1,1};
}
}
}
result->meshes = (r_mesh**)p_alloc(sizeof(void*));
result->mesh_material = (r_material**)p_alloc(sizeof(void*));
result->meshes[0] = res_model;
result->mesh_material[0] = res_material;
result->mesh_local_transform = (m4*)p_alloc(sizeof(m4));
result->mesh_local_transform[0] = m4_identity;
result->count = 1;
result->scale = v3{1,1,1};
result->position = v3{0,0,0};
result->rotation = v3{0,0,0};
wf_cleanup_obj(&obj_file);
if(has_mtl)
{
wf_cleanup_mtl(&mtl_file);
}
return true;
}
struct gltf_loader_state
{
asset_manager *am;
cgltf_data *data;
r_texture *texture_buffer;
u32 texture_capacity;
};
void gltf_loader_reserve_texture_space(gltf_loader_state *state, u32 count)
{
state->texture_capacity = count;
state->texture_buffer = assets_new_textures(state->am, state->texture_capacity);
memset(state->texture_buffer, 0, sizeof(r_texture) * state->texture_capacity);
}
r_texture *gltf_lazy_load_texture(gltf_loader_state *state, u32 texture_index, bool sRGB)
{
assert(texture_index < state->texture_capacity);
if(state->texture_buffer[texture_index].flags & R_TEXTURE_INITIALIZED)
{
bool is_buffered_sRGB = !!(state->texture_buffer[texture_index].flags & (sRGB ? R_TEXTURE_SRGB : 0));
assert(sRGB == is_buffered_sRGB);
return &state->texture_buffer[texture_index];
}
cgltf_data *data = state->data;
cgltf_buffer_view *t_view = data->images[texture_index].buffer_view;
LOG(LOG_DEBUG, "Loading texture %s, sRGB: %d", (t_view->name ? t_view->name : t_view->buffer->name), sRGB);
u8 *t_data;
s32 t_width, t_height, t_channels;
stbi_set_flip_vertically_on_load(false);
t_data = stbi_load_from_memory((u8*)t_view->buffer->data + t_view->offset, t_view->size, &t_width, &t_height, &t_channels, 4);
u32 flags = (sRGB ? R_TEXTURE_SRGB : 0);
state->texture_buffer[texture_index] = r_texture_create(t_data, v2s{t_width, t_height}, flags);
return &state->texture_buffer[texture_index];
}
void gltf_visit(gltf_loader_state *state, r_object *object, m4 transform, cgltf_node *node)
{
cgltf_data *data = state->data;
// Transform
if(node->has_matrix)
{
transform = transpose(*(m4*)node->matrix) * transform;
}
else
{
if(node->has_translation)
{
v3 t = {node->translation[0], node->translation[1], node->translation[2]};
transform = translation_v3(t) * transform;
}
if(node->has_rotation)
{
LOG(LOG_ERROR, "Quaternion rotation not supported yet");
}
if(node->has_scale)
{
v3 s = {node->scale[0], node->scale[1], node->scale[2]};
transform = scale_v3(s) * transform;
}
}
// @Feature: Skin
if(node->mesh)
{
cgltf_mesh *mesh = node->mesh;
for(cgltf_size p = 0; p < mesh->primitives_count; p++)
{
cgltf_primitive *primitive = &mesh->primitives[p];
if(primitive->type == cgltf_primitive_type_triangles)
{
// Mesh
v3 *vertices = NULL;
v3 *normals = NULL;
v3 *tangents = NULL;
v2 *uvs = NULL;
u64 vertices_count = 0;
u32 *indices = NULL;
u64 indices_count = 0;
if(primitive->indices->type != cgltf_type_scalar)
{
(LOG_ERROR, "glTF: Unhandled indices type %d", primitive->indices->type);
continue;
}
cgltf_accessor *a = primitive->indices;
indices = (u32*)p_alloc(a->count * sizeof(u32));
indices_count = a->count;
cgltf_accessor_unpack_indices(a, indices, a->count);
vertices_count = primitive->attributes[0].data->count;
for(cgltf_size i = 0; i < primitive->attributes_count; i++)
{
if(primitive->attributes[i].type == cgltf_attribute_type_position)
{
cgltf_accessor *a = primitive->attributes[i].data;
vertices = (v3*)p_alloc(a->count * sizeof(v3));
if(vertices_count != a->count)
{
LOG(LOG_ERROR, "glTF: vertex count (%d) != previously stored vertex count (%d)", a->count, vertices_count);
continue;
}
cgltf_accessor_unpack_floats(a, (f32*)vertices, a->count * 3);
}
else if(primitive->attributes[i].type == cgltf_attribute_type_normal)
{
cgltf_accessor *a = primitive->attributes[i].data;
normals = (v3*)p_alloc(a->count * sizeof(v3));
if(vertices_count != a->count)
{
LOG(LOG_ERROR, "glTF: normal count (%d) != vertex count (%d)", a->count, vertices_count);
continue;
}
cgltf_accessor_unpack_floats(a, (f32*)normals, a->count * 3);
}
else if(primitive->attributes[i].type == cgltf_attribute_type_tangent)
{
cgltf_accessor *a = primitive->attributes[i].data;
tangents = (v3*)p_alloc(a->count * sizeof(v3));
v4 *tangents_tmp = (v4*)p_alloc(a->count * sizeof(v4));
if(vertices_count != a->count)
{
LOG(LOG_ERROR, "glTF: tangent count (%d) != vertex count (%d)", a->count, vertices_count);
continue;
}
cgltf_accessor_unpack_floats(a, (f32*)tangents_tmp, a->count * 4);
for(u32 i = 0; i < a->count; i++)
tangents[i] = tangents_tmp[i].xyz;
p_free(tangents_tmp);
}
else if(primitive->attributes[i].type == cgltf_attribute_type_texcoord)
{
cgltf_accessor *a = primitive->attributes[i].data;
uvs = (v2*)p_alloc(a->count * sizeof(v2));
if(vertices_count != a->count)
{
LOG(LOG_ERROR, "glTF: uv count (%d) != vertex count (%d)", a->count, vertices_count);
continue;
}
cgltf_accessor_unpack_floats(a, (f32*)uvs, a->count * 2);
}
else
{
//LOG(LOG_WARNING, "Unmanaged attribute type: %d", primitive->attributes[i].type);
}
}
r_mesh *mesh = (r_mesh*)p_alloc(sizeof(r_mesh));
*mesh = r_mesh_create(indices_count, indices, vertices_count, vertices, normals, tangents, uvs);
// @Feature: Calculate tangents if missing
// Material
r_material *material = assets_new_materials(state->am, 1);
memset(material, 0, sizeof(r_material));
material->shader = &r_render_state.shader_pbr;
// Find image index
u32 albedo_texture_index = primitive->material->pbr_metallic_roughness.base_color_texture.texture->image - data->images;
if(albedo_texture_index > data->images_count)
LOG(LOG_ERROR, "Albedo image index (%u) out of bounds (%u)", albedo_texture_index, data->images_count);
material->albedo_texture = gltf_lazy_load_texture(state, albedo_texture_index, true);
material->albedo_factor = V4(primitive->material->pbr_metallic_roughness.base_color_factor);
// @Cleanup: @Performance: Merge metallic and roughness texture or split them up?
u32 metallic_texture_index = primitive->material->pbr_metallic_roughness.metallic_roughness_texture.texture->image - data->images;
if(metallic_texture_index > data->images_count)
LOG(LOG_ERROR, "Metallic image index (%u) out of bounds (%u)", metallic_texture_index, data->images_count);
material->metallic_texture = gltf_lazy_load_texture(state, metallic_texture_index, false);
material->metallic_factor = primitive->material->pbr_metallic_roughness.metallic_factor;
u32 roughness_texture_index = primitive->material->pbr_metallic_roughness.metallic_roughness_texture.texture->image - data->images;
if(roughness_texture_index > data->images_count)
LOG(LOG_ERROR, "Roughness image index (%u) out of bounds (%u)", roughness_texture_index, data->images_count);
material->roughness_texture = gltf_lazy_load_texture(state, roughness_texture_index, false);
material->roughness_factor = primitive->material->pbr_metallic_roughness.roughness_factor;
if(primitive->material->normal_texture.texture)
{
u32 normal_texture_index = primitive->material->normal_texture.texture->image - data->images;
if(normal_texture_index > data->images_count)
LOG(LOG_ERROR, "Normal image index (%u) out of bounds (%u)", normal_texture_index, data->images_count);
material->normal_texture = gltf_lazy_load_texture(state, normal_texture_index, false);
}
if(primitive->material->emissive_texture.texture)
{
u32 emissive_texture_index = primitive->material->emissive_texture.texture->image - data->images;
if(emissive_texture_index > data->images_count)
LOG(LOG_ERROR, "Emission image index (%u) out of bounds (%u)", emissive_texture_index, data->images_count);
material->emissive_texture = gltf_lazy_load_texture(state, emissive_texture_index, true);
f32 strength = primitive->material->has_emissive_strength ? primitive->material->has_emissive_strength : 1.0;
material->emissive_factor = V4(V3(primitive->material->emissive_factor) * strength, 1.0);
}
object->meshes = (r_mesh**)p_realloc(object->meshes, (object->count + 1) * sizeof(r_mesh));
object->mesh_material = (r_material**)p_realloc(object->mesh_material, (object->count + 1) * sizeof(r_material));
object->mesh_local_transform = (m4*)p_realloc(object->mesh_local_transform, (object->count + 1) * sizeof(m4));
object->meshes [object->count] = mesh;
object->mesh_material[object->count] = material;
object->mesh_local_transform[object->count] = transform;
object->count++;
}
else
{
LOG(LOG_ERROR, "glTF: Unhandled primitive type %d", primitive->type);
}
}
}
for(cgltf_size i = 0; i < node->children_count; i++)
{
gltf_visit(state, object, transform, node->children[i]);
}
}
bool assets_load_object_gltf(asset_manager *am, const char *filename, r_object *result)
{
gltf_loader_state state;
state.am = am;
cgltf_options options = {};
cgltf_data *data = NULL;
cgltf_result status = cgltf_parse_file(&options, filename, &data);
if(status != cgltf_result_success)
{
LOG(LOG_DEBUG, "Error parsing glTF file \"%s\".", filename);
return false;
}
status = cgltf_load_buffers(&options, data, filename);
if(status != cgltf_result_success)
{
LOG(LOG_DEBUG, "Erorr loading glTF buffers from file \"%s\".", filename);
cgltf_free(data);
return false;
}
state.data = data;
// Reserve texture space
gltf_loader_reserve_texture_space(&state, data->images_count);
// Prepare object
r_object object;
memset(&object, 0, sizeof(r_object));
object.scale = v3{1,1,1};
object.position = v3{0,0,0};
object.rotation = v3{0,0,0};
object.has_shadow = true;
for(cgltf_size i = 0; i < data->scene->nodes_count; i++)
{
gltf_visit(&state, &object, m4_identity, data->scene->nodes[i]);
}
// Resize unused space
*result = object;
cgltf_free(data);
return true;
}
bool assets_load_audio(asset_manager *am, const char *name, p_audio_buffer *result)
{
s32 music_channels;
s32 music_sample_rate;
s16 *music_data;
s32 music_samples = stb_vorbis_decode_filename(name, &music_channels, &music_sample_rate, &music_data);
result->samples = (p_audio_sample*) p_alloc(sizeof(p_audio_sample) * music_samples);
for(u32 i = 0; i < music_samples; i++)
{
result->samples[i].left = (f32)music_data[2*i] / -SHRT_MIN; // Left
result->samples[i].right = (f32)music_data[2*i + 1] / -SHRT_MIN; // Right
}
result->size = music_samples;
free(music_data);
return true;
}
bool assets_load_cubemap(asset_manager *am, const char *px, const char *nx, const char *py, const char *ny, const char *pz, const char *nz, r_cubemap *result)
{
stbi_set_flip_vertically_on_load(false);
//stbi_ldr_to_hdr_scale(1.0f);
//stbi_ldr_to_hdr_gamma(2.2f);
float *data[6];
v2s size; s32 channels;
v2s s;
data[0] = stbi_loadf(px, &s.x, &s.y, &channels, 3);
size = s;
data[1] = stbi_loadf(nx, &s.x, &s.y, &channels, 3);
assert(size == s);
data[2] = stbi_loadf(py, &s.x, &s.y, &channels, 3);
assert(size == s);
data[3] = stbi_loadf(ny, &s.x, &s.y, &channels, 3);
assert(size == s);
data[4] = stbi_loadf(pz, &s.x, &s.y, &channels, 3);
assert(size == s);
data[5] = stbi_loadf(nz, &s.x, &s.y, &channels, 3);
assert(size == s);
r_cubemap cubemap = r_cubemap_create(data, size, 0);
if(result)
*result = cubemap;
return true;
}
void assets_init(asset_manager *am)
{
am->shaders = NULL;
am->shaders_count = 0;
am->models = NULL;
am->models_count = 0;
am->textures = NULL;
am->textures_count = 0;
am->materials = NULL;
am->materials_count = 0;
am->objects = NULL;
am->objects_count = 0;
am->allocations = NULL;
am->allocations_count = 0;
}
void assets_free(asset_manager *am)
{
}
static void assets_add_allocation(asset_manager *am, void *data)
{
u32 old_count = am->allocations_count;
am->allocations_count++;
am->allocations = (void**)p_realloc(am->allocations, am->allocations_count * sizeof(void*));
am->allocations[old_count] = data;
}
r_shader *assets_new_shaders(asset_manager *am, u32 count)
{
u32 old_count = am->shaders_count;
am->shaders_count = old_count + count;
r_shader *data = (r_shader*) p_alloc(count * sizeof(r_shader));
assets_add_allocation(am, data);
am->shaders = (r_shader**) p_realloc(am->shaders, am->shaders_count * sizeof(r_shader*));
for(u32 i = 0; i < count; i++)
am->shaders[old_count + i] = data + i;
return data;
}
r_mesh *assets_new_models(asset_manager *am, u32 count)
{
u32 old_count = am->models_count;
am->models_count = old_count + count;
r_mesh *data = (r_mesh*) p_alloc(count * sizeof(r_mesh));
assets_add_allocation(am, data);
am->models = (r_mesh**) p_realloc(am->models, am->models_count * sizeof(r_mesh*));
for(u32 i = 0; i < count; i++)
am->models[old_count + i] = data + i;
return data;
}
r_texture *assets_new_textures(asset_manager *am, u32 count)
{
u32 old_count = am->textures_count;
am->textures_count = old_count + count;
r_texture *data = (r_texture*) p_alloc(count * sizeof(r_texture));
assets_add_allocation(am, data);
am->textures = (r_texture**) p_realloc(am->textures, am->textures_count * sizeof(r_texture*));
for(u32 i = 0; i < count; i++)
am->textures[old_count + i] = data + i;
return data;
}
r_material *assets_new_materials(asset_manager *am, u32 count)
{
u32 old_count = am->materials_count;
am->materials_count = old_count + count;
r_material *data = (r_material*) p_alloc(count * sizeof(r_material));
assets_add_allocation(am, data);
am->materials = (r_material**) p_realloc(am->materials, am->materials_count * sizeof(r_material*));
for(u32 i = 0; i < count; i++)
am->materials[old_count + i] = data + i;
return data;
}
r_object *assets_new_objects(asset_manager *am, u32 count)
{
u32 old_count = am->objects_count;
am->objects_count = old_count + count;
r_object *data = (r_object*) p_alloc(count * sizeof(r_object));
assets_add_allocation(am, data);
am->objects = (r_object**) p_realloc(am->objects, am->objects_count * sizeof(r_object*));
for(u32 i = 0; i < count; i++)
am->objects[old_count + i] = data + i;
return data;
}
Buffer read_file_into_buffer(const char *filename)
{
Buffer empty = { .size = 0, .data = NULL };
Buffer result;
p_file file;
bool success;
success = p_file_init(&file, filename);
if(success) {
result.size = p_file_size(&file);
result.data = (u8*)p_alloc(result.size);
success = p_file_read(&file, &result, result.size);
p_file_deinit(&file);
}
if(!success)
return empty;
return result;
}

49
code/assets.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef _PIUMA_ASSETS_H_
#define _PIUMA_ASSETS_H_
#include "lib/types.h"
#include "render/render.h"
#include "platform.h"
struct asset_manager
{
r_shader **shaders;
u32 shaders_count;
r_mesh **models;
u32 models_count;
r_texture **textures;
u32 textures_count;
r_material **materials;
u32 materials_count;
r_object **objects;
u32 objects_count;
void **allocations;
u32 allocations_count;
};
// @TODO: return pointer to loaded asset, instead of copying data into *result
bool assets_load_object_obj(asset_manager *am, const char *filename, r_object *result); // Models, textures, materials get inserted in the gamestate arrays
bool assets_load_object_gltf(asset_manager *am, const char *filename, r_object *result);
bool assets_load_audio(asset_manager *am, const char *name, p_audio_buffer *result);
bool assets_load_cubemap(asset_manager *am, const char *px, const char *nx, const char *py, const char *ny, const char *pz, const char *nz, r_cubemap *result);
void assets_init(asset_manager *am);
void assets_free(asset_manager *am);
r_shader *assets_new_shaders (asset_manager *am, u32 count);
r_mesh *assets_new_models (asset_manager *am, u32 count);
r_texture *assets_new_textures (asset_manager *am, u32 count);
r_material *assets_new_materials(asset_manager *am, u32 count);
r_object *assets_new_objects (asset_manager *am, u32 count);
// @Feature: remove/free single assets
#endif

156
code/audio.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "audio.h"
#include "enginestate.h"
#include <string.h>
void audio_cb(p_audio_buffer *buffer);
void audio_init()
{
audio_player *player = &engine.audio;
player->track_count = 0;
player->track_capacity = 32;
player->tracks = (audio_track *) p_alloc(player->track_capacity * sizeof(audio_track));
player->next_id = 0;
player->sample_rate = p_audio_sample_rate();
p_audio_register_data_callback(audio_cb);
}
track_id audio_add_track(p_audio_buffer *data, u32 flags)
{
// @Correctness: fix track if it has a different sample rate
audio_player *player = &engine.audio;
audio_track track;
track.id = player->next_id;
player->next_id++;
track.data = *data;
track.progress = 0;
track.playing = !(flags & AUDIO_PAUSED);
track.loop = !!(flags & AUDIO_LOOP);
track.free_on_finish = !!(flags & AUDIO_FREE_ON_FINISH);
track.volume = 1.0;
if (player->track_count + 1 < player->track_capacity)
{
u64 index = player->track_count;
player->tracks[index] = track;
player->track_count++;
}
else
{
u32 replace_index = 0;
for(u32 i = 0; i < player->track_count; i++)
{
audio_track *current = &player->tracks[i];
if(track.loop == current->loop)
{
replace_index = i;
break;
}
}
player->tracks[replace_index] = track;
}
return track.id;
}
void audio_remove_track(track_id id)
{
audio_player *player = &engine.audio;
for(int i = 0; i < player->track_count; i++)
{
audio_track *current = &player->tracks[i];
if(id == current->id)
{
u32 move_count = player->track_count - (i + 1);
if(current->free_on_finish)
p_free(current->data.samples);
memmove(current, current + 1, move_count * sizeof(audio_track));
player->track_count--;
break;
}
}
}
void audio_pause_track(track_id id)
{
audio_track *track = audio_track_from_id(id);
if(track)
track->playing = false;
}
void audio_play_track(track_id id)
{
audio_track *track = audio_track_from_id(id);
if(track)
track->playing = true;
}
void audio_change_track_volume(track_id id, f32 volume)
{
audio_track *track = audio_track_from_id(id);
if(track)
track->volume = volume;
}
audio_track *audio_track_from_id(track_id id)
{
audio_player *player = &engine.audio;
for(int i = 0; i < player->track_count; i++)
{
audio_track *current = &player->tracks[i];
if(id == current->id)
return current;
}
return NULL;
}
// @Correctness: audio cb modifies the array of tracks asynchronously. All r/w operations on tracks should be protected by a mutex.
void audio_cb(p_audio_buffer *buffer)
{
audio_player *player = &engine.audio;
buffer->size = minimum(buffer->size, 2048); // Low latency
for(u64 i = 0; i < buffer->size; i++)
{
buffer->samples[i].left = 0;
buffer->samples[i].right = 0;
}
for(int track_i = 0; track_i < player->track_count; track_i++)
{
audio_track *current = &player->tracks[track_i];
if(current->playing)
{
u64 remaining = current->data.size - current->progress;
u64 copy_count = minimum(remaining, buffer->size);
if(current->loop)
copy_count = buffer->size;
for(u64 i = 0; i < copy_count; i++)
{
buffer->samples[i].left += current->volume * current->data.samples[current->progress].left;
buffer->samples[i].right += current->volume * current->data.samples[current->progress].right;
current->progress = (current->progress + 1) % current->data.size;
}
if(!current->loop && current->progress == 0) // current->progress == 0 because we finished and then we looped around
{
// Finished playing. Remove track
u32 move_count = player->track_count - (track_i + 1);
if(current->free_on_finish)
p_free(current->data.samples); // @Bug: double free when the same sound is played 2 times. Find a way to signal we reached the end of the track so the user code can make its own free (if needed)
memmove(current, current + 1, move_count * sizeof(audio_track));
track_i--;
player->track_count--;
}
}
}
}

47
code/audio.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef _PIUMA_AUDIO_H_
#define _PIUMA_AUDIO_H_
#include "lib/types.h"
#include "platform.h"
typedef u64 track_id;
struct audio_track
{
track_id id;
p_audio_buffer data;
u64 progress;
bool playing;
bool loop;
bool free_on_finish;
f32 volume;
};
struct audio_player
{
audio_track *tracks;
u32 track_count;
u32 track_capacity;
track_id next_id;
u32 sample_rate;
};
enum audio_flags
{
AUDIO_NONE = 0,
AUDIO_LOOP = 1,
AUDIO_PAUSED = 2,
AUDIO_FREE_ON_FINISH = 4
};
void audio_init();
track_id audio_add_track(p_audio_buffer *data, u32 flags);
void audio_remove_track(track_id id);
void audio_pause_track(track_id id);
void audio_play_track(track_id id);
void audio_change_track_volume(track_id id, f32 volume);
audio_track *audio_track_from_id(track_id id);
#endif

107
code/camera.cpp Normal file
View File

@@ -0,0 +1,107 @@
#include "camera.h"
#include <stdio.h>
void r_camera_base_look_at(r_camera_base *c, v3 target)
{
c->direction = normalize(target - c->position);
}
m4 r_camera_base_view(r_camera_base *c)
{
return r_view_matrix(c->position, c->direction, c->up);
}
void r_camera_fps_look_at(r_camera_fps *c, v3 target)
{
v3 direction = normalize(target - c->position);
c->pitch = degrees(asin(direction.z));
direction = normalize(v3{direction.x, direction.y, 0});
c->yaw = degrees(asin(direction.y));
if(direction.x < 0)
c->yaw = 180 - c->yaw;
}
m4 r_camera_fps_view(r_camera_fps *c)
{
v3 up;
v3 direction;
up = r_camera_up_vector;
direction = r_camera_fps_direction(c);
return r_view_matrix(c->position, direction, up);
}
v3 r_camera_fps_direction(r_camera_fps *c)
{
v3 direction;
direction.x = cos(radians(c->yaw)) * cos(radians(c->pitch));
direction.y = sin(radians(c->yaw)) * cos(radians(c->pitch));
direction.z = sin(radians(c->pitch));
return direction;
}
m4 r_view_matrix(v3 position, v3 direction, v3 up)
{
v3 right = normalize(cross(up, -direction));
v3 camera_up = normalize(cross(-direction, right));
m4 result = m4_identity;
// new X axis
result.E[0][0] = right.x;
result.E[0][1] = right.y;
result.E[0][2] = right.z;
// new Y axis
result.E[1][0] = camera_up.x;
result.E[1][1] = camera_up.y;
result.E[1][2] = camera_up.z;
// new Z axis
result.E[2][0] = -direction.x;
result.E[2][1] = -direction.y;
result.E[2][2] = -direction.z;
// translation
result.E[0][3] = -dot(right , position);
result.E[1][3] = -dot(camera_up, position);
result.E[2][3] = -dot(-direction, position);
return result;
}
m4 r_perspective_matrix(f32 fov, f32 aspect_ratio, f32 near_plane, f32 far_plane)
{
m4 result = m4_zero;
f32 tanf2 = tan(radians(fov) / 2.0);
result.E[0][0] = 1.0 / (aspect_ratio * tanf2);
result.E[1][1] = 1.0 / tanf2;
result.E[2][2] = - (far_plane + near_plane) / (far_plane - near_plane);
result.E[2][3] = -2.0 * far_plane * near_plane / (far_plane - near_plane);
result.E[3][2] = -1.0;
return result;
}
m4 r_orthographic_matrix(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far)
{
m4 result = m4_zero;
result.E[0][0] = 2 / (right - left);
result.E[1][1] = 2 / (top - bottom);
result.E[2][2] = -2 / (far - near);
result.E[3][3] = 1;
result.E[0][3] = - (right + left) / (right - left);
result.E[1][3] = - (top + bottom) / (top - bottom);
result.E[2][3] = - (far + near) / (far - near);
return result;
}

38
code/camera.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef _PIUMA_CAMERA_H_
#define _PIUMA_CAMERA_H_
#include "lib/types.h"
#include "lib/math.h"
static const v3 r_camera_up_vector = {0,0,1};
struct r_camera_base
{
v3 position;
v3 direction;
v3 up;
};
void r_camera_base_look_at(r_camera_base *c, v3 target);
m4 r_camera_base_view(r_camera_base *c);
struct r_camera_fps
{
v3 position;
// Forward = +y, Right = +x, Up = +z
f32 yaw;
f32 pitch;
};
void r_camera_fps_look_at(r_camera_fps *c, v3 target);
m4 r_camera_fps_view(r_camera_fps *c);
v3 r_camera_fps_direction(r_camera_fps *c);
m4 r_view_matrix(v3 position, v3 direction, v3 up);
m4 r_perspective_matrix(f32 fov, f32 aspect_ratio, f32 near_plane, f32 far_plane);
m4 r_orthographic_matrix(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
#endif

73
code/debug/log_viewer.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "log_viewer.h"
#include "../lib/math.h"
#include <stdio.h>
#include "../gui/gui.h"
LogViewer LogViewer::Init(Logger *logger)
{
LogViewer viewer;
viewer.logger = logger;
return viewer;
}
void LogViewer::Print_New_Messages_On_Console()
{
u64 message_count = Logger_MessageCount(logger);
if(message_count <= 0)
return;
u64 last_message_index = message_count - 1;
LogEntry *last_message = Logger_MessageAt(logger, last_message_index);
u64 id_diff = last_message->id - next_id_to_print_on_console;
u64 index_start = (last_message_index > id_diff) ? (last_message_index - id_diff) : 0;
u64 max_id = next_id_to_print_on_console;
for(int i = index_start; i < message_count; i++)
{
LogEntry *entry = Logger_MessageAt(logger, i);
if(entry->id >= next_id_to_print_on_console)
{
// printf("%lu ", entry->id);
// if (entry->level >= LOG_DEBUG) printf("DEBUG");
// else if(entry->level >= LOG_INFO) printf("INFO");
// else if(entry->level >= LOG_WARNING) printf("WARNING");
// else if(entry->level >= LOG_ERROR) printf("ERROR");
// printf(": ");
fprintf((entry->level < LOG_INFO ? stderr : stdout), "%s\n", entry->message);
max_id = maximum(max_id, entry->id + 1);
}
}
next_id_to_print_on_console = max_id;
}
void LogViewer::Draw_New_Messages_On_GUI()
{
u64 messages_to_draw = 5;
u64 message_count = Logger_MessageCount(logger);
if(message_count <= 0)
return;
Rect r = { .position = {0, (f32)global_gui_state.default_context.height}, .size = {0,0} };
messages_to_draw = minimum(messages_to_draw, message_count);
for(u64 i = 0; i < messages_to_draw; i++)
{
u64 index = message_count - 1 - i;
LogEntry *entry = Logger_MessageAt(logger, index);
char id_string[24]; sprintf(id_string, "%lu: ", entry->id);
v2 id_size = gui_text_compute_size(id_string);
v2 message_size = gui_text_compute_size(entry->message);
r.position.y -= maximum(id_size.y, message_size.y);
r.position.x = 0;
r.size = id_size;
gui_text(r, id_string);
r.position.x += id_size.x;
r.size = message_size;
gui_text(r, entry->message);
}
}

20
code/debug/log_viewer.h Normal file
View File

@@ -0,0 +1,20 @@
#ifndef _PIUMA_LOG_VIEWER_H_
#define _PIUMA_LOG_VIEWER_H_
#include "logger.h"
struct LogViewer
{
Logger *logger = NULL;
LogId next_id_to_print_on_console = 0;
static LogViewer Init(Logger *logger);
void Print_New_Messages_On_Console();
void Draw_New_Messages_On_GUI();
};
#endif

94
code/debug/logger.cpp Normal file
View File

@@ -0,0 +1,94 @@
#include "logger.h"
#include "../platform.h"
#include <assert.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
struct Logger global_logger;
void Logger_Init(struct Logger *logger)
{
assert(logger != NULL);
u32 initial_capacity = 512;
logger->message_queue = Queue_Alloc(p_alloc, struct LogEntry, initial_capacity);
}
void Logger_Deinit(struct Logger *logger)
{
assert(logger != NULL);
Logger_Clear(logger);
Queue_Free(p_free, logger->message_queue);
}
void Logger_Clear(struct Logger *logger)
{
assert(logger != NULL);
while(Queue_Size(logger->message_queue) > 0)
{
Logger_RemoveOldestMessage(logger);
}
}
void Logger_AddMessage(struct Logger *logger, LogSourceInfo source_info, LogLevel level, const char *format_message, ...)
{
assert(logger != NULL);
// Build formatted message
char *message;
va_list var_args;
va_start(var_args, format_message);
int bytes_size = vsnprintf(NULL, 0, format_message, var_args) + 1;
va_end(var_args);
message = (char *) p_alloc(bytes_size * sizeof(char));
va_start(var_args, format_message);
int written = vsnprintf(message, bytes_size, format_message, var_args) + 1;
va_end(var_args);
// Add log entry to queue, checking if we have enough space
struct LogEntry entry = {
.level = level,
.message = message,
.id = logger->last_id++,
.source = source_info
};
// fprintf(stderr, message);
// fprintf(stderr, "\n");
if(Queue_Size(logger->message_queue) >= QUEUE_HEADER_PTR(logger->message_queue)->capacity)
{
logger->overflow_count++;
Logger_RemoveOldestMessage(logger);
}
Queue_Push(logger->message_queue, entry);
}
void Logger_RemoveOldestMessage(struct Logger *logger)
{
assert(logger != NULL);
assert(Queue_Size(logger->message_queue) > 0);
struct LogEntry entry = Queue_Pop(logger->message_queue);
p_free(entry.message);
}
u64 Logger_MessageCount(struct Logger *logger)
{
assert(logger != NULL);
return Queue_Size(logger->message_queue);
}
struct LogEntry *Logger_MessageAt(struct Logger *logger, u64 index)
{
assert(logger != NULL);
assert(Queue_Size(logger->message_queue) > index);
return &Queue_At(logger->message_queue, index);
}

59
code/debug/logger.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef _PIUMA_LOGGER_H_
#define _PIUMA_LOGGER_H_
#include "../lib/types.h"
#include "../lib/queue.h"
typedef u8 LogLevel;
#define LOG_ERROR 0
#define LOG_WARNING 50
#define LOG_INFO 100
#define LOG_DEBUG 150
struct LogSourceInfo
{
const char *filename;
u32 line;
const char *function;
};
typedef u64 LogId;
struct LogEntry
{
LogLevel level;
char *message;
LogId id;
LogSourceInfo source;
};
struct Logger
{
QUEUE_TYPE(struct LogEntry) message_queue = NULL;
LogId last_id = 0;
u64 overflow_count = 0; // Number of times the buffer was full, but we added a message anyway removing the oldest one
};
void Logger_Init(struct Logger *logger);
void Logger_Deinit(struct Logger *logger);
void Logger_Clear(struct Logger *logger);
void Logger_AddMessage(struct Logger *logger, LogSourceInfo source_info, LogLevel level, const char *format_message, ...);
void Logger_RemoveOldestMessage(struct Logger *logger);
u64 Logger_MessageCount(struct Logger *logger);
struct LogEntry *Logger_MessageAt(struct Logger *logger, u64 index);
extern struct Logger global_logger;
#define LOG_INIT() Logger_Init(&global_logger)
#define LOG_DEINIT() Logger_Deinit(&global_logger)
#define LOG_CLEAR() Logger_Clear(&global_logger)
#define LOG(level, /*format_message,*/ ...) Logger_AddMessage(&global_logger, LogSourceInfo{.filename = __FILE__, .line = __LINE__, .function = __func__}, level, /*format_message,*/ __VA_ARGS__)
#endif

39
code/enginestate.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "enginestate.h"
#include "platform.h"
#include "assets.h"
#include "debug/logger.h"
engine_state engine;
bool enginestate_init()
{
u32 status;
assets_init(&engine.am);
engine.input.mouse_sensitivity = 1.4;
engine.input.movement_sensitivity = 7;
engine.input.init = true;
engine.input.target = INPUT_TARGET_PLAYER;
engine.input.player_movement = {0, 0};
engine.input.camera_rotation = {0, 0, 0};
engine.input.camera_movement = {0, 0, 0};
audio_init();
engine.time = 0;
engine.delta_t = 0;
engine.gui_scaling = 14;
engine.world = phy_create_world({0, 0, -9.81});
return true;
}
void enginestate_deinit()
{
phy_destroy_world(engine.world);
// @Correctness: complete this
}

56
code/enginestate.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef _PIUMA_ENGINESTATE_H_
#define _PIUMA_ENGINESTATE_H_
#include "lib/types.h"
#include "lib/math.h"
#include "render/render.h"
#include "audio.h"
#include "camera.h"
#include "assets.h"
#include "physics/physics.h"
enum input_target
{
INPUT_TARGET_PLAYER,
INPUT_TARGET_CAMERA,
INPUT_TARGET_EDITOR
};
struct es_input
{
f32 mouse_sensitivity;
f32 movement_sensitivity;
bool init;
enum input_target target;
v2s player_movement;
v3 camera_rotation;
v3 camera_movement;
};
struct engine_state
{
es_input input;
audio_player audio;
asset_manager am;
f64 time;
f64 delta_t;
f32 gui_scaling;
phy_world *world;
};
extern engine_state engine;
bool enginestate_init();
void enginestate_deinit();
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
#ifndef _PIUMA_WAVEFRONT_H_
#define _PIUMA_WAVEFRONT_H_
#include "../lib/types.h"
#include "../lib/math.h"
typedef void *(*wf_alloc_t)(u64 size);
typedef void *(*wf_realloc_t)(void *ptr, u64 size);
typedef void (*wf_free_t)(void *ptr);
/* Reassing wf_alloc and wf_free with your own alloc/free functions if you don't
* want to use malloc and free.
*/
extern wf_alloc_t wf_alloc;
extern wf_realloc_t wf_realloc;
extern wf_free_t wf_free;
typedef u32 wf_index;
typedef u32 wf_size;
// Indices are 1-based in Wavefront. 0 is used to signal the index was omitted.
struct wf_face_index
{
wf_index vertex;
wf_index texture;
wf_index normal;
};
struct wf_face
{
wf_face_index indices[3];
};
struct wf_object
{
char *name;
// @Feature: groups are not supported yet
char *material_name;
// Vertices
v4 *vertices;
wf_size vertices_count;
// Texture coordinates
v3 *texture_coords;
wf_size texture_coords_count;
// Vertex normals
v3 *normals;
wf_size normals_count;
// Faces
wf_face *faces;
wf_size faces_count;
};
struct wf_obj_file
{
char *name;
char **mtllib_names;
wf_size mtllib_names_count;
wf_object *objects;
wf_size objects_count;
};
struct wf_material
{
char *name;
v3 Ka; // ambient color
v3 Kd; // diffuse color
v3 Ke; // emissive color
v3 Ks; // specular color
f32 Ns; // specular exposure
f32 d; // dissolved - transparency where 0.0 is fully transparent and 1.0 is opaque
f32 Ni; // optical density - refractive index
int illum; // illumination model
/* illum is described at https://en.wikipedia.org/wiki/Wavefront_.obj_file#Basic_materials as follows:
* 0. Color on and Ambient off
* 1. Color on and Ambient on
* 2. Highlight on
* 3. Reflection on and Ray trace on
* 4. Transparency: Glass on, Reflection: Ray trace on
* 5. Reflection: Fresnel on and Ray trace on
* 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
* 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
* 8. Reflection on and Ray trace off
* 9. Transparency: Glass on, Reflection: Ray trace off
* 10. Casts shadows onto invisible surfaces
*/
char *map_Ka; // ambient texture map name
char *map_Kd; // diffuse texture map name
char *map_Ke; // emissive texture map name
char *map_Ks; // specular color texture map name
char *map_Ns; // specular highlight texture map name
char *map_d; // alpha texture map name
char *bump; // bump map name
char *disp; // displacement map name
char *decal; // stencil decal texture map name
// @Feature: Texture options are not supported yet
};
struct wf_mtl_file
{
char *name;
wf_material *materials;
wf_size materials_count;
};
bool wf_parse_obj(char *data, u64 size, const char *name, wf_obj_file *return_obj);
bool wf_parse_mtl(char *data, u64 size, const char *name, wf_mtl_file *return_mtl);
void wf_cleanup_obj(wf_obj_file *obj_file);
void wf_cleanup_mtl(wf_mtl_file *mtl_file);
#endif

807
code/gui/gui.cpp Normal file
View File

@@ -0,0 +1,807 @@
#include "gui.h"
#include "../render/2d.h"
#include "../render/render.h"
#include "../lib/math.h"
#include "../lib/color.h"
#include "text_draw.h"
#include "../debug/logger.h"
#include "stdio.h"
#include "string.h"
#include "../platform.h"
#include "../lib/hashing.h"
Gui_State global_gui_state;
void gui_button_draw_inner_text(Gui_Context *ctx, Rect r, const char *text, v4 color, Rect *actual_drawn_rect = NULL);
bool gui_init()
{
gui_context_init(&global_gui_state.default_context);
global_gui_state.selected_context = &global_gui_state.default_context;
bool success = gui_text_draw_init();
return success;
}
void gui_deinit()
{
gui_text_draw_deinit();
}
void gui_context_init(Gui_Context *ctx)
{
ctx->width = 100;
ctx->height = 100;
ctx-> last_frame_time = 0;
ctx->current_frame_time = 0;
ctx-> active = NULL;
ctx-> hot = NULL;
ctx->possibly_hot = NULL;
ctx->active_start_time = 0;
ctx-> hot_start_time = 0;
ctx->active_status = 0;
ctx->text_cursor_position = 0;
ctx->text_length = 0;
ctx->windows = NULL;
ctx->window_count = 0;
ctx->window_capacity = 0;
ctx->current_window = NULL;
ctx->id_count = 0;
ctx->id_capacity = 8;
ctx->id_stack = (Gui_Id*)p_alloc(sizeof(Gui_Id) * ctx->id_capacity);
ctx->input.pointer_position = {0, 0};
ctx->input.absolute_pointer_position = {0, 0};
ctx->input.mouse_pressed = false;
ctx->input.mouse_pressed_this_frame = false;
ctx->input.mouse_released_this_frame = false;
ctx->input.text_cursor_move = 0;
ctx->input.text[0] = '\0';
ctx->input.absolute_pointer_position_last_frame = {0, 0};
ctx->style.font_size = 12;
ctx->style.animation_base_time = 0.100;
ctx->style.text_color = v4{1.0, 1.0, 1.0, 1.0};
ctx->style.text_align = GUI_ALIGN_CENTER;
ctx->style.button_color = v4{0.4f, 0.4f, 0.4f, 1.0f}*0.8f;
ctx->style.button_color_hovered = v4{0.3f, 0.3f, 0.3f, 1.0f}*0.9f;
ctx->style.button_color_pressed = v4{0.1f, 0.1f, 0.1f, 1.0f};
ctx->style.button_text_color = v4{1.0f, 1.0f, 1.0f, 1.0f};
ctx->style.button_text_color_hovered = v4{1.0f, 1.0f, 1.0f, 1.0f};
ctx->style.button_text_color_pressed = v4{1.0f, 1.0f, 1.0f, 1.0f};
ctx->style.button_radius = 3;
ctx->style.slider_fill_color = {0.0f, 0.3f, 0.9f, 1.0f};
ctx->style.window_background_color = {0.01,0.01,0.01, 0.98};
ctx->style.window_border_color = {1.0,0.06,0.0,1.0};
ctx->style.window_background_color_inactive = {0.05,0.05,0.05, 0.95};
ctx->style.window_border_color_inactive = {0.3,0.3,0.3, 1.0};
ctx->style.window_corner_radius = 5;
ctx->style.window_titlebar_color = ctx->style.window_border_color;
ctx->style.window_titlebar_color_inactive = {0.1,0.1,0.1,0.1};
}
void gui_context_select(Gui_Context *ctx)
{
global_gui_state.selected_context = ctx;
}
void gui_frame_begin(Gui_Context *ctx, f64 curr_time)
{
ctx-> last_frame_time = ctx->current_frame_time;
ctx->current_frame_time = curr_time;
}
void gui_frame_begin(f64 curr_time)
{
gui_frame_begin(&global_gui_state.default_context, curr_time);
}
void gui_frame_end(Gui_Context *ctx)
{
// Render windows
for(u32 i = 0; i < ctx->window_count; i++)
{
Gui_Window *w = &ctx->windows[i];
if(w->still_open)
{
Rect r_uv = Rect{0,0,1,-1}; // y is flipped when rendering framebuffer's textures
r_2d_immediate_rectangle(w->r, v4{1,1,1,1}, r_uv, &w->framebuffer.color_texture);
w->still_open = false; // Will be set to true if still open
}
}
// @Performance: cleanup unused windows
// Fix state
if(ctx->hot != ctx->possibly_hot)
{
ctx->hot = ctx->possibly_hot;
ctx->hot_start_time = ctx->current_frame_time;
}
ctx->input.mouse_pressed_this_frame = false;
ctx->input.mouse_released_this_frame = false;
ctx->input.absolute_pointer_position_last_frame = ctx->input.absolute_pointer_position;
ctx->input.text[0] = '\0';
ctx->input.text_cursor_move = 0;
ctx->possibly_hot = NULL;
}
void gui_frame_end()
{
gui_frame_end(&global_gui_state.default_context);
}
void gui_handle_event(Gui_Context *ctx, Event *e)
{
switch(e->type)
{
case EVENT_MOUSE_MOVE: {
if(!e->mouse_move.relative)
{
ctx->input.pointer_position = e->mouse_move.position;
ctx->input.absolute_pointer_position = e->mouse_move.position;
}
} break;
case EVENT_KEY: {
switch(e->key.key_code)
{
case KEY_MOUSE_LEFT: {
ctx->input.mouse_pressed = e->key.pressed;
if(e->key.pressed)
ctx->input.mouse_pressed_this_frame = true;
else
ctx->input.mouse_released_this_frame = true;
} break;
case KEY_ARROW_LEFT: {
if(e->key.pressed)
ctx->input.text_cursor_move--;
} break;
case KEY_ARROW_RIGHT: {
if(e->key.pressed)
ctx->input.text_cursor_move++;
} break;
default: {
} break;
}
} break;
case EVENT_RESIZE: {
} break;
case EVENT_TEXT: {
strcat(ctx->input.text, e->text.data);
} break;
default: {
} break;
}
}
void gui_handle_event(Event *e)
{
gui_handle_event(&global_gui_state.default_context, e);
}
// ### Widgets ###
// Text
void gui_text(Gui_Context *ctx, Rect r, const char *text)
{
// @Feature: Clip text to Rect r
gui_text_draw(r, text, ctx->style.font_size, ctx->style.text_color);
}
void gui_text(Rect r, const char *text)
{
gui_text(&global_gui_state.default_context, r, text);
}
void gui_text_aligned(Gui_Context *ctx, Rect r, const char *text, Gui_Text_Align alignment)
{
// @Cleanup: this should not depend on setting state. We should have a function that gets alignment as an argument
Gui_Text_Align old_alignment = ctx->style.text_align;
ctx->style.text_align = alignment;
gui_button_draw_inner_text(ctx, r, text, ctx->style.text_color);
ctx->style.text_align = old_alignment;
}
void gui_text_aligned(Rect r, const char *text, Gui_Text_Align alignment)
{
gui_text_aligned(&global_gui_state.default_context, r, text, alignment);
}
v2 gui_text_compute_size(Gui_Context *ctx, const char *text)
{
return gui_text_draw_size(text, ctx->style.font_size);
}
v2 gui_text_compute_size(const char *text)
{
return gui_text_compute_size(&global_gui_state.default_context, text);
}
// Button
bool gui_button(Gui_Context *ctx, Rect r, const char *text)
{
Gui_Id widget_id = gui_id_from_pointer(ctx, text);
bool behaviuor = gui_button_behaviuor(ctx, widget_id, r);
// Compute colors
v4 button_color = ctx->style.button_color;
v4 text_color = ctx->style.button_text_color;
{
if(ctx->hot == widget_id)
{
f64 delta_t = (ctx->current_frame_time - ctx->hot_start_time);
f32 interpolation = clamp(0, 1, delta_t / ctx->style.animation_base_time);
button_color = lerp(ctx->style.button_color, ctx->style.button_color_hovered, interpolation);
text_color = lerp(ctx->style.button_text_color, ctx->style.button_text_color_hovered, interpolation);
}
if(ctx->active == widget_id)
{
f64 delta_t = (ctx->current_frame_time - ctx->active_start_time);
f32 interpolation = clamp(0, 1, delta_t / ctx->style.animation_base_time);
button_color = lerp(ctx->style.button_color_hovered, ctx->style.button_color_pressed, interpolation * 0.4 + 0.6);
text_color = lerp(ctx->style.button_text_color_hovered, ctx->style.button_text_color_pressed, interpolation * 0.4 + 0.6);
}
}
// Draw button and text
r_2d_immediate_rounded_rectangle(r, ctx->style.button_radius, button_color);
gui_button_draw_inner_text(ctx, r, text, text_color);
return behaviuor;
}
bool gui_button(Rect r, const char *text)
{
return gui_button(&global_gui_state.default_context, r, text);
}
void gui_button_draw_inner_text(Gui_Context *ctx, Rect r, const char *text, v4 color, Rect *actual_drawn_rect)
{
v2 text_size = gui_text_draw_size(text, ctx->style.font_size);
// Alignment (center, left, right)
v2 text_position = r.position + (r.size - text_size) * v2{0.5, 0.5};
if(ctx->style.text_align == GUI_ALIGN_LEFT)
text_position = r.position + (r.size - text_size) * v2{0, 0.5};
if(ctx->style.text_align == GUI_ALIGN_RIGHT)
text_position = r.position + (r.size - text_size) * v2{1, 0.5};
// Draw
Rect text_rect = { .position = text_position, .size = text_size };
// @Feature: Clip text to Rect r
gui_text_draw(text_rect, text, ctx->style.font_size, color);
if(actual_drawn_rect)
*actual_drawn_rect = text_rect;
}
// Slider
bool gui_slider(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value)
{
Gui_Id widget_id = gui_id_from_pointer(ctx, value);
bool behaviour = gui_button_behaviuor(ctx, widget_id, r);
if(ctx->active == widget_id)
{
f32 pointer_ratio = (ctx->input.pointer_position.x - r.position.x) / r.size.x;
*value = clamp(0.0f, 1.0f, pointer_ratio) * (max - min) + min;
}
// Colors
v4 button_color = ctx->style.button_color;
v4 text_color = ctx->style.button_text_color;
{
f64 delta_t = (ctx->current_frame_time - ctx->hot_start_time);
f32 interpolation = sin(10 * delta_t) * 0.5 + 0.5;
if(ctx->hot == widget_id)
{
button_color = lerp(ctx->style.button_color, ctx->style.button_color_hovered, interpolation);
text_color = lerp(ctx->style.button_text_color, ctx->style.button_text_color_hovered, interpolation);
}
if(ctx->active == widget_id)
{
button_color = lerp(ctx->style.button_color_hovered, ctx->style.button_color_pressed, interpolation * 0.4 + 0.6);
text_color = lerp(ctx->style.button_text_color_hovered, ctx->style.button_text_color_pressed, interpolation * 0.4 + 0.6);
}
}
// Draw
f32 border = 2;
f32 radius = ctx->style.button_radius;
// Draw background
v4 background_color = ctx->style.button_color;
r_2d_immediate_rounded_rectangle(r, radius, background_color); // Background
// Draw fill
f32 ratio = (*value - min) / (max - min);
Rect fill_r = r;
fill_r.position += v2{border, border};
fill_r.size = v2{maximum(0, fill_r.size.x - 2*border), maximum(0, fill_r.size.y - 2*border)};
f32 fill_radius = maximum(0, radius - border);
fill_r.size.x = fill_r.size.x * ratio;
r_2d_immediate_rounded_rectangle(fill_r, fill_radius, ctx->style.slider_fill_color);
// Draw border
v4 border_color = ctx->style.button_color_pressed;
Rect border_r = r;
border_r.position += v2{border, border} * 0.5;
border_r.size = v2{maximum(0, border_r.size.x - border), maximum(0, border_r.size.y - border)};
f32 border_radius = maximum(0, radius - border*0.5);
r_2d_immediate_rounded_rectangle_outline(border_r, border_radius, border_color, border);
// Draw value text
char text[64];
sprintf(text, "%f", *value);
gui_button_draw_inner_text(ctx, r, text, text_color);
return behaviour || ctx->active == widget_id;
}
bool gui_slider(Rect r, f32 min, f32 max, f32 *value)
{
return gui_slider(&global_gui_state.default_context, r, min, max, value);
}
// Images
bool gui_image(Gui_Context *ctx, Rect r, r_texture *texture)
{
Gui_Id widget_id = gui_id_from_pointer(ctx, texture);
v4 color = {1,1,1,1};
r_2d_immediate_rectangle(r, color, {0,0,1,1}, texture);
return gui_button_behaviuor(ctx, widget_id, r);;
}
bool gui_image(Rect r, r_texture *texture)
{
return gui_image(&global_gui_state.default_context, r, texture);
}
bool gui_image(Gui_Context *ctx, Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags)
{
r_texture texture = r_texture_create((u8*)bmp, {width, height}, flags | R_TEXTURE_DONT_OWN);
bool result = gui_image(ctx, r, &texture);
r_texture_destroy(&texture);
return result;
}
bool gui_image(Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags)
{
return gui_image(&global_gui_state.default_context, r, bmp, width, height, channels, flags);
}
// Text input
bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size)
{
Gui_Id widget_id = gui_id_from_pointer(ctx, text);
bool behaviour = gui_text_input_behaviuor(ctx, widget_id, r);
bool edited = false;
// Cursor, mouse click, input from keyboard/os
if(ctx->active == widget_id && ctx->input.mouse_pressed_this_frame)
{
ctx->text_length = strlen(text);
ctx->text_cursor_position = ctx->text_length;
}
// Move cursors between UTF8 codepoints (not bytes)
if(ctx->input.text_cursor_move != 0)
{
while(ctx->input.text_cursor_move > 0)
{
if(text[ctx->text_cursor_position] == '\0')
{
ctx->input.text_cursor_move = 0;
break;
}
ctx->text_cursor_position += utf8_bytes_to_next_valid_codepoint(text, ctx->text_cursor_position);
ctx->input.text_cursor_move--;
}
while(ctx->input.text_cursor_move < 0)
{
if(ctx->text_cursor_position == 0)
{
ctx->input.text_cursor_move = 0;
break;
}
ctx->text_cursor_position -= utf8_bytes_to_prev_valid_codepoint(text, ctx->text_cursor_position);
ctx->input.text_cursor_move++;
}
}
if(ctx->active == widget_id && ctx->input.text[0] != 0)
{
// @Bug: Should iterate on utf8 codepoints. If we don't, there's the possibility
// of inserting half of a multi-byte codepoint.
for(char *c = ctx->input.text; *c != 0; c++)
{
if(*c == 0x08) // Backspace
{
if(ctx->text_cursor_position > 0)
{
u32 codepoint_bytes = utf8_bytes_to_prev_valid_codepoint(text, ctx->text_cursor_position);
u64 from_index = ctx->text_cursor_position;
u64 to_index = ctx->text_cursor_position - codepoint_bytes;
memmove(text + to_index, text + from_index, ctx->text_length + 1 - from_index);
ctx->text_length -= codepoint_bytes;
ctx->text_cursor_position -= codepoint_bytes;
}
continue;
}
if(*c == 0x7F) // Delete
{
if(ctx->text_cursor_position < ctx->text_length)
{
u32 codepoint_bytes = utf8_bytes_to_next_valid_codepoint(text, ctx->text_cursor_position);
u64 from_index = ctx->text_cursor_position + codepoint_bytes;
u64 to_index = ctx->text_cursor_position;
memmove(text + to_index, text + from_index, ctx->text_length + 1 - from_index);
ctx->text_length -= codepoint_bytes;
}
continue;
}
if(ctx->text_length < max_size - 1) // Leave space for 0 terminator
{
memmove(text + ctx->text_cursor_position + 1, text + ctx->text_cursor_position, ctx->text_length + 1 - ctx->text_cursor_position);
text[ctx->text_cursor_position] = *c;
ctx->text_length += 1;
ctx->text_cursor_position += 1;
}
}
edited = true;
}
r_2d_immediate_rounded_rectangle(r, ctx->style.button_radius, ctx->style.button_color);
Rect text_rect;
gui_button_draw_inner_text(ctx, r, text, ctx->style.button_text_color, &text_rect);
if(ctx->active == widget_id)
{
// Draw cursor
f64 delta_t = ctx->current_frame_time - ctx->active_start_time;
f32 u = clamp(0, 1, sin(delta_t * 5) * 0.7 + 0.6);
v4 cursor_color = ctx->style.button_text_color;
cursor_color *= lerp(0, cursor_color.a, u);
char replaced = text[ctx->text_cursor_position];
text[ctx->text_cursor_position] = 0;
v2 cursor_position;
v2 text_size = gui_text_draw_size(text, ctx->style.font_size, &cursor_position);
text[ctx->text_cursor_position] = replaced;
Rect cursor_rect =
{
.position = text_rect.position + cursor_position - v2{0, ctx->style.font_size},
.size = ctx->style.font_size * v2{0.1, 0.9}
};
r_2d_immediate_rectangle(cursor_rect, cursor_color);
}
return edited;
}
bool gui_text_input(Rect r, char *text, u64 max_size)
{
return gui_text_input(&global_gui_state.default_context, r, text, max_size);
}
// Windows
bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id)
{
gui_id_stack_push(ctx, id);
Gui_Window *window = gui_window_by_id(ctx, r, id);
window->still_open = true;
gui_window_update_rect(ctx, window, r);
u32 window_index = window - ctx->windows;
bool hovered = gui_is_hovered(ctx, id, r);
if(hovered && ctx->input.mouse_pressed_this_frame)
{
// Bring window on top
u32 move_count = ctx->window_count - 1 - window_index;
if(move_count > 0)
{
Gui_Window tmp = *window;
memmove(ctx->windows + window_index, ctx->windows + window_index + 1, sizeof(Gui_Window) * move_count);
ctx->windows[ctx->window_count - 1] = tmp;
window_index = ctx->window_count - 1;
window = &ctx->windows[window_index];
}
}
ctx->current_window = window;
ctx->input.pointer_position = ctx->input.absolute_pointer_position - window->r.position;
ctx->old_framebuffer = r_render_state.current_framebuffer;
r_framebuffer_select(&window->framebuffer);
bool is_inactive = window_index != ctx->window_count-1;
v4 background_color = is_inactive ? ctx->style.window_background_color_inactive :
ctx->style.window_background_color;
v4 border_color = is_inactive ? ctx->style.window_border_color_inactive :
ctx->style.window_border_color;
r_clear({0,0,0,0});
Rect background_rect = {0.5, 0.5, floor(r.w)-1.0, floor(r.h)-1.0};
r_2d_immediate_rounded_rectangle(background_rect, ctx->style.window_corner_radius, background_color);
r_2d_immediate_rounded_rectangle_outline(background_rect, ctx->style.window_corner_radius, border_color, 1.0);
return true;
}
bool gui_window_start(Rect r, Gui_Id id)
{
return gui_window_start(&global_gui_state.default_context, r, id);
}
void gui_window_end(Gui_Context *ctx)
{
gui_id_stack_pop(ctx);
ctx->current_window = NULL;
ctx->input.pointer_position = ctx->input.absolute_pointer_position;
r_framebuffer_select(ctx->old_framebuffer);
}
void gui_window_end()
{
return gui_window_end(&global_gui_state.default_context);
}
bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *close, v2 *move)
{
Gui_Id widget_id = gui_id_from_pointer(ctx, title);
bool behaviour = gui_button_behaviuor(ctx, widget_id, r);
if(close)
*close = false;
if(move)
*move = {0,0};
// Background
v4 titlebar_color = ctx->style.window_titlebar_color_inactive;
if(ctx->current_window && ctx->current_window - ctx->windows == ctx->window_count - 1)
{
titlebar_color = ctx->style.window_titlebar_color;
}
r_2d_immediate_rounded_rectangle(r, ctx->style.window_corner_radius, titlebar_color);
// Title
v2 title_size = gui_text_compute_size(title);
Rect title_r = r;
title_r.size = title_size;
title_r.position = r.position + (r.size - title_r.size) / 2;
gui_text(title_r, title);
// Exit button
f32 smallest_side = minimum(r.w, r.h);
f32 exit_size = smallest_side;
Gui_Style exit_style = ctx->style;
exit_style.button_color = v4{0.8f, 0.8f, 0.8f, 1.0f}*0.0f;
exit_style.button_color_hovered = v4{1.0f, 0.0f, 0.0f, 1.0f}*1.0f;
exit_style.button_color_pressed = v4{0.8f, 0.0f, 0.0f, 1.0f};
exit_style.button_text_color = v4{1.0f, 1.0f, 1.0f, 1.0f};
exit_style.button_text_color_hovered = v4{1.0f, 1.0f, 1.0f, 1.0f};
exit_style.button_text_color_pressed = v4{1.0f, 1.0f, 1.0f, 1.0f};
exit_style.button_radius = ctx->style.window_corner_radius;
Gui_Style old_style = ctx->style;
ctx->style = exit_style;
Rect exit_button_r = {r.x + r.w - exit_size, r.y, exit_size, exit_size};
if(gui_button(ctx, exit_button_r, ""))
{
if(close)
*close = true;
behaviour = true;
}
ctx->style = old_style;
// Move
if(ctx->active == widget_id && !ctx->input.mouse_pressed_this_frame)
{
if(move)
{
*move = ctx->input.absolute_pointer_position - ctx->input.absolute_pointer_position_last_frame;
}
}
return behaviour || ctx->active == widget_id;
}
bool gui_window_titlebar(Rect r, const char *title, bool *close, v2 *move)
{
return gui_window_titlebar(&global_gui_state.default_context, r, title, close, move);
}
Gui_Window *gui_window_by_id(Gui_Context *ctx, Rect r, Gui_Id id)
{
Gui_Window *window = NULL;
for(u32 i = 0; i < ctx->window_count; i++)
{
if(ctx->windows[i].id == id)
{
window = &ctx->windows[i];
break;
}
}
if(!window)
{
if(ctx->window_count >= ctx->window_capacity)
{
if(ctx->window_capacity == 0)
ctx->window_capacity = 1;
ctx->window_capacity *= 2;
ctx->windows = (Gui_Window*) p_realloc(ctx->windows, sizeof(Gui_Window) * ctx->window_capacity);
}
window = &ctx->windows[ctx->window_count];
ctx->window_count++;
window->id = id;
window->r = r;
window->framebuffer = r_framebuffer_create(V2S(r.size), 0);
}
return window;
}
void gui_window_update_rect(Gui_Context *ctx, Gui_Window *window, Rect r)
{
if(window->r.size != r.size)
{
r_framebuffer_update_size(&window->framebuffer, V2S(r.size));
}
window->r = r;
}
// Helpers
bool gui_is_hovered(Gui_Context *ctx, Gui_Id widget_id, Rect r)
{
if(is_inside(r, ctx->input.pointer_position))
{
s32 current_window_index = -1; // We use -1 to indicate we are not in a window. When we iterate over windows we do a +1 and start from 0, aka the first window. If we used 0, we would start from 1 and skip over window index 0.
// The ctx->windows array is sorted from back to front. If we are inside a window, only the following windows in the array can overlap up. The ones before are covered by the current window.
if(ctx->current_window)
current_window_index = ctx->current_window - ctx->windows;
// Am I a window? If so, we start checking from us. If ctx->current_window is set and widget_id is a window, it means we are a subwindow.
// Subwindow are not supported yet though (20 September 2023), so this should be a bug in the user code. Yeah we don't check to prevent this, but anyways.
for(s32 i = current_window_index + 1; i < ctx->window_count; i++)
{
Gui_Id window_id = ctx->windows[i].id;
if(widget_id == window_id)
{
current_window_index = i;
break;
}
}
// Iterate over windows that cover the current one
for(u32 i = current_window_index + 1; i < ctx->window_count; i++)
{
Gui_Id window_id = ctx->windows[i].id;
Rect window_rect = ctx->windows[i].r;
if(is_inside(window_rect, ctx->input.absolute_pointer_position))
{
return false;
}
}
return true;
}
return false;
}
bool gui_button_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r)
{
bool behaviour = false;
if(gui_is_hovered(ctx, widget_id, r))
{
if(!ctx->active || ctx->active == widget_id || !(ctx->active_status & GUI_WIDGET_STATUS_PREVENT_HOT))
ctx->possibly_hot = widget_id;
if(ctx->hot == widget_id && ctx->input.mouse_pressed_this_frame)
{
ctx->active = widget_id;
ctx->active_start_time = ctx->current_frame_time;
ctx->active_status = GUI_WIDGET_STATUS_PREVENT_HOT;
}
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
{
behaviour = true;
}
}
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
{
ctx->active = NULL;
ctx->active_status = 0;
}
return behaviour;
}
bool gui_text_input_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r)
{
bool behaviour = false;
if(gui_is_hovered(ctx, widget_id, r))
{
if(!ctx->active || ctx->active == widget_id || !(ctx->active_status & GUI_WIDGET_STATUS_PREVENT_HOT))
ctx->possibly_hot = widget_id;
if(ctx->hot == widget_id && ctx->input.mouse_pressed_this_frame)
{
ctx->active = widget_id;
ctx->active_start_time = ctx->current_frame_time;
ctx->active_status = 0;
}
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
{
behaviour = true;
}
}
if(ctx->active == widget_id && ctx->input.mouse_released_this_frame)
{
// ctx->active = NULL;
// ctx->active_status = 0;
}
return behaviour;
}
Gui_Id gui_id_from_pointer(Gui_Context *ctx, const void* ptr)
{
u32 seed = 0xFFFFFFFF;
if(ctx->id_count)
seed = ctx->id_stack[ctx->id_count - 1];
return hash_crc32(&ptr, sizeof(void*), seed);
}
void gui_id_stack_push(Gui_Context *ctx, Gui_Id id)
{
if(ctx->id_capacity <= ctx->id_count)
{
u32 new_capacity = maximum(ctx->id_count + 1, ctx->id_capacity * 2);
ctx->id_stack = (Gui_Id*)p_realloc(ctx->id_stack, sizeof(Gui_Id) * new_capacity);
ctx->id_capacity = new_capacity;
}
ctx->id_stack[ctx->id_count] = id;
ctx->id_count++;
}
void gui_id_stack_pop(Gui_Context *ctx)
{
if(ctx->id_count > 0)
ctx->id_count--;
}

195
code/gui/gui.h Normal file
View File

@@ -0,0 +1,195 @@
#ifndef _PIUMA_GUI_H_
#define _PIUMA_GUI_H_
#include "../lib/types.h"
#include "../lib/math.h"
#include "../lib/geometry.h"
#include "../render/2d.h"
#include "../lib/text.h"
#include "../lib/event.h"
typedef u32 Gui_Id;
struct Gui_Input
{
v2 pointer_position;
v2 absolute_pointer_position;
bool mouse_pressed;
bool mouse_pressed_this_frame;
bool mouse_released_this_frame;
s64 text_cursor_move;
char text[32];
v2 absolute_pointer_position_last_frame;
};
enum Gui_Text_Align
{
GUI_ALIGN_CENTER,
GUI_ALIGN_LEFT,
GUI_ALIGN_RIGHT
};
struct Gui_Style
{
f32 font_size;
f32 animation_base_time;
v4 text_color;
Gui_Text_Align text_align;
v4 button_color;
v4 button_color_hovered;
v4 button_color_pressed;
v4 button_text_color;
v4 button_text_color_hovered;
v4 button_text_color_pressed;
f32 button_radius;
v4 slider_fill_color;
v4 window_background_color;
v4 window_border_color;
v4 window_background_color_inactive;
v4 window_border_color_inactive;
f32 window_corner_radius;
v4 window_titlebar_color;
v4 window_titlebar_color_inactive;
};
struct Gui_Window
{
Gui_Id id;
Rect r;
r_framebuffer framebuffer;
bool still_open;
};
enum Gui_Widget_Status_Flags : u8
{
GUI_WIDGET_STATUS_NONE,
GUI_WIDGET_STATUS_PREVENT_HOT
};
struct Gui_Context
{
u32 width;
u32 height;
f64 last_frame_time;
f64 current_frame_time;
Gui_Id active; // Are we interacting with the widget (pressed for buttons, ready to receive text for text inputs)
Gui_Id hot; // Hovered the previous frame
Gui_Id possibly_hot; // Might become hot this frame
f64 active_start_time;
f64 hot_start_time;
u8 active_status;
// Text input state
u64 text_cursor_position;
u64 text_length;
// Windows
Gui_Window *windows;
u32 window_count;
u32 window_capacity;
Gui_Window *current_window;
r_framebuffer *old_framebuffer;
// ID
Gui_Id *id_stack;
u32 id_count;
u32 id_capacity;
Gui_Input input;
Gui_Style style;
};
struct Gui_State
{
Gui_Context default_context;
Gui_Context *selected_context;
};
extern Gui_State global_gui_state;
bool gui_init();
void gui_deinit();
void gui_context_init(Gui_Context *ctx);
//@Correctness: gui_context_deinit
void gui_context_select(Gui_Context *ctx); // Set implicit Gui_Context
void gui_frame_begin(Gui_Context *ctx, f64 curr_time);
void gui_frame_begin(f64 curr_time);
void gui_frame_end(Gui_Context *ctx);
void gui_frame_end();
void gui_handle_event(Gui_Context *ctx, Event *e);
void gui_handle_event(Event *e);
// ### Widgets ###
// Text
void gui_text(Gui_Context *ctx, Rect r, const char *text);
void gui_text(Rect r, const char *text);
void gui_text_aligned(Gui_Context *ctx, Rect r, const char *text, Gui_Text_Align alignment);
void gui_text_aligned(Rect r, const char *text, Gui_Text_Align alignment);
v2 gui_text_compute_size(Gui_Context *ctx, const char *text);
v2 gui_text_compute_size(const char *text);
// Button
bool gui_button(Gui_Context *ctx, Rect r, const char *text);
bool gui_button(Rect r, const char *text);
// Slider
bool gui_slider(Gui_Context *ctx, Rect r, f32 min, f32 max, f32 *value);
bool gui_slider(Rect r, f32 min, f32 max, f32 *value);
// Checkbox
// Option buttons
// Combo box
// Tooltips
// Images
bool gui_image(Gui_Context *ctx, Rect r, r_texture *texture);
bool gui_image(Rect r, r_texture *texture);
bool gui_image(Gui_Context *ctx, Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags = 0);
bool gui_image(Rect r, const u8 *bmp, u32 width, u32 height, u32 channels, u32 flags = 0);
// Text input
bool gui_text_input(Gui_Context *ctx, Rect r, char *text, u64 max_size);
bool gui_text_input(Rect r, char *text, u64 max_size);
// Windows
bool gui_window_start(Gui_Context *ctx, Rect r, Gui_Id id); // You have to provide some kind of unique id to identify windows
bool gui_window_start(Rect r, Gui_Id id);
void gui_window_end(Gui_Context *ctx);
void gui_window_end();
bool gui_window_titlebar(Gui_Context *ctx, Rect r, const char *title, bool *close, v2 *move);
bool gui_window_titlebar(Rect r, const char *title, bool *close, v2 *move);
Gui_Window *gui_window_by_id(Gui_Context *ctx, Rect r, Gui_Id id); // Rect r might be needed for creation
void gui_window_update_rect(Gui_Context *ctx, Gui_Window *window, Rect r);
// Helpers
bool gui_is_hovered (Gui_Context *ctx, Gui_Id widget_id, Rect r);
bool gui_button_behaviuor (Gui_Context *ctx, Gui_Id widget_id, Rect r);
bool gui_text_input_behaviuor(Gui_Context *ctx, Gui_Id widget_id, Rect r);
bool gui_window_behaviour (Gui_Context *ctx, Gui_Id widget_id, Rect r);
Gui_Id gui_id_from_pointer(Gui_Context *ctx, const void* ptr);
void gui_id_stack_push(Gui_Context *ctx, Gui_Id id);
void gui_id_stack_pop(Gui_Context *ctx);
#endif

159
code/gui/layout.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include "layout.h"
#include "gui.h"
// Grid
Rect Gui_Layout_Grid::rect()
{
return last_rect;
}
void Gui_Layout_Grid::row(u32 count)
{
cursor.x = 0;
cursor.y += count;
}
Rect Gui_Layout_Grid::cell(u32 count)
{
count = minimum(count, max_cells_count.x);
s32 free_space_in_row = max_cells_count.x - cursor.x;
if(free_space_in_row < count)
row();
last_rect = rect_at(cursor, {count, 1});
cursor.x += count;
return last_rect;
}
Rect Gui_Layout_Grid::rect_at(v2s cell_index, v2s size)
{
Rect result;
v2 cell_border_v = v2{cell_border, cell_border};
result.position = window_position + cell_border_v + (cell_border_v + cell_size) * V2(cell_index);
result.size = cell_size * V2(size) + cell_border_v * (V2(size - v2s{1,1}));
return result;
}
Gui_Layout_Grid gui_layout_grid_create_by_divisions(v2 position, v2 window_size, u32 divisions_x, u32 divisions_y, f32 border)
{
Gui_Layout_Grid layout;
layout.window_position = position;
layout.window_size = window_size;
layout.cell_size = (window_size - v2{1,1}*border) / v2{divisions_x, divisions_y} - v2{1,1}*border;
layout.cell_border = border;
layout.max_cells_count = v2s{divisions_x, divisions_y};
layout.cursor = {0,0};
layout.last_rect = layout.rect_at({0,0});
return layout;
}
Gui_Layout_Grid gui_layout_grid_create_by_cell_size(v2 position, v2 window_size, v2 cell_size, f32 border)
{
Gui_Layout_Grid layout;
layout.window_position = position;
layout.window_size = window_size;
layout.cell_size = cell_size;
layout.cell_border = border;
layout.max_cells_count = V2S((window_size - v2{1,1}*border) / (cell_size + v2{1,1}*border));
layout.cursor = {0,0};
layout.last_rect = layout.rect_at({0,0});
return layout;
}
// Basic
Rect Gui_Layout_Basic::rect()
{
return last_rect;
}
void Gui_Layout_Basic::push_rect(Rect r)
{
cursor.x = cursor.x + r.size.x + element_distance.x;
cursor.y = cursor.y;
last_rect = r;
next_row_y = maximum(next_row_y, cursor.y + r.size.y + element_distance.y);
}
void Gui_Layout_Basic::row()
{
cursor.x = window_padding.x;
cursor.y = next_row_y;
next_row_y = cursor.y + font_size + element_distance.y;
}
Rect Gui_Layout_Basic::label_rect(const char *text)
{
Rect r;
r.size.y = font_size;
r.size.x = gui_text_compute_size(text).x;
f32 remaining_space_x = window_size.x - 2*window_padding.x - cursor.x;
if(cursor.x != window_padding.x && remaining_space_x < r.size.x)
row();
r.position = window_position + cursor;
push_rect(r);
return r;
}
Rect Gui_Layout_Basic::button_rect(const char *text)
{
Rect r;
r.size.y = font_size;
r.size.x = gui_text_compute_size(text).x;
r.size += 2*button_padding;
f32 remaining_space_x = window_size.x - 2*window_padding.x - cursor.x;
if(cursor.x != window_padding.x && remaining_space_x < r.size.x)
row();
r.position = window_position + cursor;
push_rect(r);
return r;
}
Rect Gui_Layout_Basic::button_rect(f32 length)
{
Rect r;
r.size.y = font_size;
r.size.x = length;
r.size.y += 2*button_padding.y; // No x padding in this case. We already have an assigned length
f32 remaining_space_x = window_size.x - 2*window_padding.x - cursor.x;
if(cursor.x != window_padding.x && remaining_space_x < r.size.x)
row();
r.position = window_position + cursor;
push_rect(r);
return r;
}
Gui_Layout_Basic gui_layout_basic_create(v2 position, v2 size, f32 font_size)
{
Gui_Layout_Basic layout;
layout.window_position = position;
layout.window_size = size;
layout.cursor = {0,0};
layout.next_row_y = 0;
layout.last_rect = {0,0,0,0};
layout.font_size = font_size;
layout.button_padding = {0,0};
layout.element_distance = {0,0};
layout.window_padding = {0,0};
return layout;
}

57
code/gui/layout.h Normal file
View File

@@ -0,0 +1,57 @@
#ifndef _PIUMA_GUI_LAYOUT_H_
#define _PIUMA_GUI_LAYOUT_H_
#include "../lib/types.h"
#include "../lib/geometry.h"
// Grid
const s32 GUI_LAYOUT_MAX_CELLS = 0x7FFFFFFF;
struct Gui_Layout_Grid
{
v2 window_position;
v2 window_size;
v2 cell_size;
f32 cell_border;
v2s max_cells_count;
v2s cursor;
Rect last_rect;
Rect rect(); // Get last rect
void row(u32 count = 1);
Rect cell(u32 count = 1);
Rect rect_at(v2s position, v2s size = {1,1}); // Does not modify cursor. You have to assign it yourself.
};
Gui_Layout_Grid gui_layout_grid_create_by_divisions(v2 position, v2 window_size, u32 divisions_x, u32 divisions_y, f32 border = 0);
Gui_Layout_Grid gui_layout_grid_create_by_cell_size(v2 position, v2 window_size, v2 cell_size, f32 border = 0);
// Basic
struct Gui_Layout_Basic
{
v2 window_position;
v2 window_size;
v2 cursor = {0,0};
f32 next_row_y = 0;
Rect last_rect = {0,0,0,0};
f32 font_size;
v2 button_padding = {0,0};
v2 element_distance = {0,0};
v2 window_padding = {0,0};
Rect rect();
void push_rect(Rect r);
void row();
Rect label_rect(const char *text = "");
Rect button_rect(const char *text = "");
Rect button_rect(f32 length);
};
Gui_Layout_Basic gui_layout_basic_create(v2 position, v2 size, f32 font_size);
#endif

548
code/gui/text_draw.cpp Normal file
View File

@@ -0,0 +1,548 @@
#include "text_draw.h"
#include "../lib/color.h"
#include "../lib/math.h"
#include "../lib/geometry.h"
#include "../lib/text.h"
#include "../lib/ds.h"
#include "../platform.h"
#include "stb_truetype.h"
#include "../debug/logger.h"
#include "../assets.h"
#include "../enginestate.h"
#include <string.h>
#include <stdlib.h>
struct gui_glyph_info
{
utf8_codepoint codepoint;
Rect box; // .position = offset from position for alignment, .size = size of the bitmap to draw
v2u position; // Position of the top left border in the texture
s32 advance;
u32 next; // Anything >= container_capacity is to be considered NULL
u32 previous;
};
/* Glyph caching:
* We store a small number of sizes and a fairly large number of characters for each size.
* Each slot will have the same width and height, so that we can easily replace it without
* re-packing everything. This will also make the code simpler.
* We use 0xFFFFFFFF as a placeholder for empty slots.
*
*/
struct gui_glyph_codepoint_map
{
utf8_codepoint codepoint;
u32 index;
};
struct gui_glyph_texture
{
f32 font_size;
struct gui_glyph_codepoint_map *sorted_indices;
gui_glyph_info *info;
u32 oldest;
u32 newest;
u32 capacity;
v2s glyph_max_size;
r_texture *texture;
};
struct gui_glyph_cache
{
gui_glyph_texture *glyphs;
u32 capacity;
u32 oldest;
u32 max_glyphs_per_texture;
};
static void gui_glyph_cache_init();
static void gui_glyph_cache_deinit();
static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity);
static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs);
static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count);
// Globals
static u8 *Font_File;
static stbtt_fontinfo Font_Info;
static gui_glyph_cache Glyph_Cache;
bool gui_text_draw_init()
{
// @Feature: user specified font
// @Cleanup: one-line file read
p_file f;
p_file_init(&f, "assets/fonts/DejaVuSerif-Bold.ttf");
Buffer buf;
buf.size = p_file_size(&f);
buf.data = (u8*)p_alloc(buf.size + 1);
buf.data[buf.size] = '\0';
p_file_read(&f, &buf, buf.size);
p_file_deinit(&f);
Font_File = buf.data;
if(!stbtt_InitFont(&Font_Info, Font_File, 0))
{
LOG(LOG_ERROR, "Cannot load font.");
return false;
}
gui_glyph_cache_init();
return true;
}
void gui_text_draw_deinit()
{
gui_glyph_cache_deinit();
p_free(Font_File);
}
v2 gui_text_draw_size(const char *text, f32 font_size, v2 *cursor_position)
{
// UTF8 conversion
u64 text_length = utf8_codepoint_count(text);
utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
{
u64 bytes_read = 0;
text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
}
// Compute size
v2 result = gui_utf8_text_draw_size(codepoints, text_length, font_size, cursor_position);
p_free(codepoints);
return result;
}
void gui_text_draw(Rect r, const char *text, f32 font_size, v4 color)
{
// UTF8 conversion
u64 text_length = utf8_codepoint_count(text);
utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
{
u64 bytes_read = 0;
text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
}
// Draw
gui_utf8_text_draw(r, codepoints, text_length, font_size, color);
p_free(codepoints);
}
v2 gui_utf8_text_draw_size(const utf8_codepoint *text, u64 length, f32 font_size, v2 *cursor_position)
{
f32 font_scale;
s32 font_ascent;
s32 font_descent;
s32 font_line_gap;
s32 font_baseline;
{
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
font_baseline = font_ascent;
font_ascent *= font_scale;
font_descent *= font_scale;
font_line_gap *= font_scale;
font_baseline *= font_scale;
}
// Compute size
v2 size = {0, (f32)(font_ascent - font_descent)};
{
v2 cursor = {0, (f32)(font_ascent - font_descent)};
for(u64 i = 0; i < length; i++)
{
s32 advance, lsb;
stbtt_GetCodepointHMetrics(&Font_Info, text[i], &advance, &lsb);
// Special characters
if(text[i] == 10) // '\n'
{
advance = 0;
cursor.x = 0;
cursor.y += font_ascent - font_descent + font_line_gap;
size.y = cursor.y;
}
// Normal characters
cursor.x += floor(advance * font_scale); // Remember to consider kerning
size.x = maximum(size.x, cursor.x);
}
if(cursor_position)
*cursor_position = cursor;
}
return size;
}
void gui_utf8_text_draw(Rect r, const utf8_codepoint *text, u64 length, f32 font_size, v4 color)
{
f32 font_scale;
s32 font_ascent;
s32 font_descent;
s32 font_line_gap;
s32 font_baseline;
{
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
font_baseline = font_ascent;
font_ascent *= font_scale;
font_descent *= font_scale;
font_line_gap *= font_scale;
font_baseline *= font_scale;
}
// Compute glyphs
gui_glyph_texture *glyphs = gui_glyph_cache_texture_for_codepoints(font_size, text, length);
// Map text to quads
v2 *vertices;
v2 *uvs;
u64 draw_count = 0;
{
vertices = (v2*) p_alloc(6 * length * sizeof(v2)); // 2 triangles, 3 vertices each = 6 vertices
uvs = (v2*) p_alloc(6 * length * sizeof(v2));
v2 position = r.position;
position.y += font_baseline;
for(u64 i = 0; i < length; i++)
{
gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&text[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
if(found == NULL)
{
LOG(LOG_ERROR, "Cannot find codepoint 0x%X in glyph list.", text[i]);
continue;
}
u64 glyph_i = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
gui_glyph_info *info = &glyphs->info[glyph_i];
// Special characters
if(text[i] == 10) // '\n'
{
position.x = r.position.x;
position.y += font_ascent - font_descent + font_line_gap;
}
// Normal characters
// Map character to its vertices
{
Rect r;
Rect r_uv;
r.position = position + info->box.position;
r.position.x = floor(r.position.x + 0.5);
r.position.y = floor(r.position.y + 0.5);
r.size = info->box.size;
r_uv.position = V2(info->position) / V2(glyphs->texture->size);
r_uv.size = info->box.size / V2(glyphs->texture->size);
v2 a = r.position + v2{r.w, 0 };
v2 b = r.position + v2{0 , 0 };
v2 c = r.position + v2{0 , r.h};
v2 d = r.position + v2{r.w, r.h};
v2 a_uv = r_uv.position + v2{r_uv.w, 0 };
v2 b_uv = r_uv.position + v2{0 , 0 };
v2 c_uv = r_uv.position + v2{0 , r_uv.h};
v2 d_uv = r_uv.position + v2{r_uv.w, r_uv.h};
vertices[6*draw_count + 0] = a;
vertices[6*draw_count + 1] = b;
vertices[6*draw_count + 2] = c;
vertices[6*draw_count + 3] = a;
vertices[6*draw_count + 4] = c;
vertices[6*draw_count + 5] = d;
uvs[6*draw_count + 0] = a_uv;
uvs[6*draw_count + 1] = b_uv;
uvs[6*draw_count + 2] = c_uv;
uvs[6*draw_count + 3] = a_uv;
uvs[6*draw_count + 4] = c_uv;
uvs[6*draw_count + 5] = d_uv;
}
position.x += info->advance; // Remember to consider kerning
draw_count++;
}
}
// Render quads
r_2d_immediate_mesh(6*draw_count, vertices, color, uvs, glyphs->texture);
p_free(vertices);
p_free(uvs);
}
static void gui_glyph_cache_init()
{
Glyph_Cache.capacity = 4;
Glyph_Cache.oldest = 0;
Glyph_Cache.glyphs = (gui_glyph_texture*)p_alloc(sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
memset(Glyph_Cache.glyphs, 0, sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
Glyph_Cache.max_glyphs_per_texture = 256; // @Correctness: test with small values, to trigger the glyph replacement code
}
static void gui_glyph_cache_deinit()
{
for(u32 i = 0; i < Glyph_Cache.capacity; i++)
gui_glyph_texture_destroy(&Glyph_Cache.glyphs[i]);
p_free(Glyph_Cache.glyphs);
Glyph_Cache.glyphs = NULL;
Glyph_Cache.capacity = 0;
Glyph_Cache.oldest = 0;
}
static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity)
{
// Init container for glyphs and info
gui_glyph_texture glyphs;
glyphs.font_size = font_size;
glyphs.sorted_indices = (gui_glyph_codepoint_map*) p_alloc(sizeof(gui_glyph_codepoint_map) * capacity);
glyphs.info = (gui_glyph_info*) p_alloc(sizeof(gui_glyph_info) * capacity);
glyphs.oldest = 0;
glyphs.newest = capacity - 1;
glyphs.capacity = capacity;
// Estimate max glyph size.
// @Correctness: Text draw will fail if a bigger glyph is used.
f32 font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
utf8_codepoint cp[] = {' ', 'M', 'j', '{', '=', 'w', 'W'};
glyphs.glyph_max_size = v2s{0, 0};
for(s32 i = 0; i < sizeof(cp)/sizeof(utf8_codepoint); i++)
{
v2s top_left, bottom_right;
stbtt_GetCodepointBitmapBox(&Font_Info, cp[i], font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
v2s size = bottom_right - top_left;
glyphs.glyph_max_size.x = maximum(glyphs.glyph_max_size.x, size.x);
glyphs.glyph_max_size.y = maximum(glyphs.glyph_max_size.y, size.y);
}
LOG(LOG_DEBUG, "Font size %f not in cache. Slot size (%d %d)", font_size, glyphs.glyph_max_size.x, glyphs.glyph_max_size.y);
// Precompile some info data
for(u32 i = 0; i < glyphs.capacity; i++)
{
glyphs.info[i] = gui_glyph_info{
.codepoint = 0xFFFFFFFF,
.box = {0,0,0,0},
.position = v2u{glyphs.glyph_max_size.x * i, 0},
.advance = 0,
.next = i + 1, // Last .next will be >= capacity (== capacity to be precise), so we will consider it to be NULL
.previous = ((i == 0) ? glyphs.capacity : (i - 1))
};
glyphs.sorted_indices[i] = gui_glyph_codepoint_map{0xFFFFFFFF, i};
}
// Initialize texture
v2s texture_size = v2s{glyphs.glyph_max_size.x * glyphs.capacity, glyphs.glyph_max_size.y};
u8 *texture_data = (u8*)p_alloc(sizeof(u8) * texture_size.x * texture_size.y);
memset(texture_data, 0, sizeof(u8) * texture_size.x * texture_size.y);
glyphs.texture = assets_new_textures(&engine.am, 1);
*glyphs.texture = r_texture_create(texture_data, texture_size, R_TEXTURE_ALPHA | R_TEXTURE_NO_MIPMAP);
return glyphs;
}
static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs)
{
if(glyphs->sorted_indices)
p_free(glyphs->sorted_indices);
if(glyphs->info)
p_free(glyphs->info);
if(glyphs->texture)
r_texture_destroy(glyphs->texture);
glyphs->sorted_indices = NULL;
glyphs->info = NULL;
glyphs->texture = NULL;
glyphs->font_size = 0;
glyphs->oldest = 0;
glyphs->newest = 0;
glyphs->capacity = 0;
}
static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count)
{
// Approximate font_size. We don't really want to build different bitmaps for size 12.000000 and 12.000001.
// This will also prevent floating point rounding errors from rebuilding the cache.
font_size = floor(font_size * 10) / 10;
// Find cached texture for this size or build a new one
gui_glyph_texture *glyphs = NULL;
for(u32 i = 0; i < Glyph_Cache.capacity; i++)
{
//LOG(LOG_DEBUG, "Font size: %f - Cached: %f", font_size, Glyph_Cache.glyphs[i].font_size);
if(abs(Glyph_Cache.glyphs[i].font_size - font_size) < 0.01)
{
glyphs = &Glyph_Cache.glyphs[i];
}
}
if(glyphs == NULL)
{
//LOG(LOG_DEBUG, "Size not matched");
glyphs = &Glyph_Cache.glyphs[Glyph_Cache.oldest];
Glyph_Cache.oldest = (Glyph_Cache.oldest + 1) % Glyph_Cache.capacity;
gui_glyph_texture_destroy(glyphs);
*glyphs = gui_glyph_texture_create(font_size, Glyph_Cache.max_glyphs_per_texture);
}
// Build list of unique codepoints (so that we don't render the same codepoint twice)
utf8_codepoint *unique = (utf8_codepoint*) p_alloc(count * sizeof(utf8_codepoint));
memcpy(unique, codepoints, count * sizeof(utf8_codepoint));
u64 unique_count = make_unique(unique, count, sizeof(utf8_codepoint), u32_cmp);
if(unique_count > glyphs->capacity)
LOG(LOG_ERROR, "Unique codepoints count > cache capacity. Some codepoints will not be rendered.");
// Find which codepoints are not already in the cache and need to be rendered
utf8_codepoint to_render[unique_count];
u32 to_render_count = 0;
for(u32 i = 0; i < unique_count; i++)
{
gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&unique[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
if(found == NULL)
{
// Not found -> add to the list of glyphs to render
to_render[to_render_count] = unique[i];
to_render_count++;
}
else
{
// Found -> mark it as recent, so that is does not get deleted prematurely
u32 index = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
if(index == glyphs->newest)
{
// Already the newest. Do nothing.
}
else if(index == glyphs->oldest)
{
// We have no previous to fix, only next
u32 next = glyphs->info[index].next;
glyphs->info[next].previous = glyphs->capacity; // Next is the new oldest -> no previous
glyphs->oldest = next;
// Set index as last element
glyphs->info[index].next = glyphs->capacity;
glyphs->info[index].previous = glyphs->newest;
glyphs->info[glyphs->newest].next = index;
glyphs->newest = index;
}
else
{
// We in between the list. We have both previous and next elements to fix.
u32 previous = glyphs->info[index].previous;
u32 next = glyphs->info[index].next;
glyphs->info[previous].next = next;
glyphs->info[next].previous = previous;
// Set index as last element
glyphs->info[index].next = glyphs->capacity;
glyphs->info[index].previous = glyphs->newest;
glyphs->info[glyphs->newest].next = index;
glyphs->newest = index;
}
}
}
p_free(unique);
// Get info for rendering
f32 font_scale;
s32 font_ascent;
s32 font_descent;
s32 font_line_gap;
s32 font_baseline;
{
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
font_baseline = font_ascent;
font_ascent *= font_scale;
font_descent *= font_scale;
font_line_gap *= font_scale;
font_baseline *= font_scale;
}
// Render glyph in its appropriate place
for(u32 i = 0; i < to_render_count; i++)
{
u32 index = glyphs->oldest;
glyphs->oldest = glyphs->info[index].next;
glyphs->info[glyphs->oldest].previous = glyphs->capacity;
glyphs->info[index].next = glyphs->capacity;
glyphs->info[index].previous = glyphs->newest;
glyphs->info[index].codepoint = to_render[i];
glyphs->info[glyphs->newest].next = index;
glyphs->newest = index;
// Complete gui_glyph_info structure and render
gui_glyph_info *info = &glyphs->info[index];
v2s top_left, bottom_right;
stbtt_GetCodepointBitmapBox(&Font_Info, info->codepoint, font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
s32 advance, lsb;
stbtt_GetCodepointHMetrics(&Font_Info, info->codepoint, &advance, &lsb);
v2s size = bottom_right - top_left;
// Special codepoints
if(info->codepoint == 10) // '\n'
{
size = {0, 0};
advance = 0;
}
info->box.position = V2(top_left);
info->box.size = V2(size);
//info->position = v2u{glyphs->glyph_max_size.x * index, 0}; // Commented because it's already pre-computed.
info->advance = advance * font_scale;
// @Correctness: needs to be premultiplied alpha
stbtt_MakeCodepointBitmap(&Font_Info, glyphs->texture->data + info->position.x, info->box.size.x, info->box.size.y, glyphs->texture->size.x, font_scale, font_scale, info->codepoint);
r_texture_update(glyphs->texture, glyphs->texture->data + info->position.x, V2S(info->box.size), V2S(info->position), glyphs->texture->size.x);
}
// Build sorted array with indices that point to the element
u32 nonempty_count = glyphs->capacity;
for(u32 i = 0; i < glyphs->capacity; i++)
{
glyphs->sorted_indices[i] = gui_glyph_codepoint_map{glyphs->info[i].codepoint, i};
if(glyphs->info[i].codepoint == 0xFFFFFFFF)
{
// When the cache is mostly empty, this makes the sorting way faster.
nonempty_count = i;
break;
}
}
qsort(glyphs->sorted_indices, nonempty_count, sizeof(gui_glyph_codepoint_map), u32_cmp);
return glyphs;
}

18
code/gui/text_draw.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef _PIUMA_GUI_TEXT_DRAW_H_
#define _PIUMA_GUI_TEXT_DRAW_H_
#include "../lib/types.h"
#include "../lib/math.h"
#include "../lib/geometry.h"
#include "../lib/text.h"
bool gui_text_draw_init();
void gui_text_draw_deinit();
v2 gui_text_draw_size(const char *text, f32 font_size, v2 *cursor_position = NULL);
void gui_text_draw(Rect r, const char *text, f32 font_size, v4 color);
v2 gui_utf8_text_draw_size(const utf8_codepoint *text, u64 length, f32 font_size, v2 *cursor_position = NULL);
void gui_utf8_text_draw(Rect r, const utf8_codepoint *text, u64 length, f32 font_size, v4 color);
#endif

12
code/lib/all.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef _PIUMA_LIB_ALL_H_
#define _PIUMA_LIB_ALL_H_
#include "types.h"
#include "ds.h"
#include "queue.h"
#include "math.h"
#include "geometry.h"
#include "color.h"
#include "text.h"
#endif

26
code/lib/bits.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef _PIUMA_LIB_BITS_H_
#define _PIUMA_LIB_BITS_H_
#include "types.h"
inline u32 bits_endian_swap(u32 x)
{
return
(x << 24 ) |
(x << 8 & 0x00FF0000) |
(x >> 8 & 0x0000FF00) |
(x >> 24 );
}
inline u32 bits_reverse(u32 x)
{
u32 result = 0;
for(int i = 0; i < 32; i++)
{
result = result << 1 | (x & 1);
x = x >> 1;
}
return result;
}
#endif

25
code/lib/color.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef _PIUMA_LIB_COLOR_H_
#define _PIUMA_LIB_COLOR_H_
#include "math.h"
#include "types.h"
inline u32 convert_color_to_ABGR_u32(v4 color)
{
u32 r = 0xFF * color.r;
u32 g = 0xFF * color.g;
u32 b = 0xFF * color.b;
u32 a = 0xFF * color.a;
return (a << 24) | (b << 16) | (g << 8) | (r);
}
inline v4 convert_ABGR_u32_to_color(u32 color)
{
f32 r = (f32)((color >> 0) & 0xFF) / (f32)(0xFF);
f32 g = (f32)((color >> 8) & 0xFF) / (f32)(0xFF);
f32 b = (f32)((color >> 16) & 0xFF) / (f32)(0xFF);
f32 a = (f32)((color >> 24) & 0xFF) / (f32)(0xFF);
return v4{r, g, b, a};
}
#endif

87
code/lib/ds.cpp Normal file
View File

@@ -0,0 +1,87 @@
#include "ds.h"
#include <string.h>
int char_cmp(const void *a, const void *b)
{
char _a = *(char*)a;
char _b = *(char*)b;
if(_a < _b) return -1;
if(_a > _b) return +1;
return 0;
}
int u8_cmp (const void *a, const void *b)
{
u8 _a = *(u8*)a;
u8 _b = *(u8*)b;
if(_a < _b) return -1;
if(_a > _b) return +1;
return 0;
}
int u32_cmp(const void *a, const void *b)
{
u32 _a = *(u32*)a;
u32 _b = *(u32*)b;
if(_a < _b) return -1;
if(_a > _b) return +1;
return 0;
}
int u64_cmp(const void *a, const void *b)
{
u64 _a = *(u64*)a;
u64 _b = *(u64*)b;
if(_a < _b) return -1;
if(_a > _b) return +1;
return 0;
}
int s32_cmp(const void *a, const void *b)
{
s32 _a = *(s32*)a;
s32 _b = *(s32*)b;
if(_a < _b) return -1;
if(_a > _b) return +1;
return 0;
}
int s64_cmp(const void *a, const void *b)
{
s64 _a = *(s64*)a;
s64 _b = *(s64*)b;
if(_a < _b) return -1;
if(_a > _b) return +1;
return 0;
}
u64 make_unique(void *array, u64 count, u64 element_size, compare_fn *cmp)
{
qsort(array, count, element_size, cmp);
// Remove duplicates
u8 *start = (u8*)array;
u8 *end = start + element_size * count;
u64 deleted = 0;
u8 *prev = start;
u8 *curr = start + element_size;
while(curr < end)
{
if(cmp(prev, curr) == 0)
{
deleted++;
}
else
{
prev += element_size;
memcpy(prev, curr, element_size);
}
curr += element_size;
}
return count - deleted;
}

29
code/lib/ds.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef _PIUMA_LIB_DS_H_
#define _PIUMA_LIB_DS_H_
#include "types.h"
#include "stdlib.h"
typedef int compare_fn(const void *, const void *);
// Basic compare functions
int char_cmp(const void *a, const void *b);
int u8_cmp (const void *a, const void *b);
int u32_cmp(const void *a, const void *b);
int u64_cmp(const void *a, const void *b);
int s32_cmp(const void *a, const void *b);
int s64_cmp(const void *a, const void *b);
// Modifies "array" and returns the number of unique things
u64 make_unique(void *array, u64 count, u64 element_size, compare_fn *cmp);
// @Cleanup: put this in the right place
template<typename T>
void swap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
#endif

220
code/lib/event.h Normal file
View File

@@ -0,0 +1,220 @@
#ifndef _PIUMA_LIB_EVENT_H_
#define _PIUMA_LIB_EVENT_H_
#include "types.h"
#include "math.h"
// Mouse events
// Keyboard events
// Window events (resize, hide, focus)
// Quit
// Other OS events (signals?)
enum Event_Type
{
EVENT_NONE,
EVENT_QUIT,
EVENT_MOUSE_MOVE,
EVENT_KEY,
EVENT_RESIZE,
EVENT_FOCUS,
EVENT_UNFOCUS,
EVENT_TEXT,
EVENT_COUNT
};
// @Performance: a lot of this codes are sequential in Linux. Check with other OSs if that's the case. If it is, we can have faster mapping by subtracting and adding an offset.
enum Key_Code
{
KEY_UNKNOWN = 0,
// @Correctness: check all ascii characters that are on a keyboard
KEY_ENTER = '\r',
KEY_ESCAPE = '\e',
KEY_BACKSPACE = '\b',
KEY_TAB = '\t',
KEY_SPACE = ' ',
KEY_EXCLAMATION = '!',
KEY_DOUBLE_QUOTE = '"',
KEY_HASH = '#',
KEY_PERCENT = '%',
KEY_DOLLAR = '$',
KEY_AMPERSAND = '&',
KEY_SINGLE_QUOTE = '\'',
KEY_LEFT_PARENTHESIS = '(',
KEY_RIGHT_PARENTHESIS = ')',
KEY_ASTERISK = '*',
KEY_PLUS = '+',
KEY_COMMA = ',',
KEY_MINUS = '-',
KEY_PERIOD = '.',
KEY_SLASH = '/',
KEY_0 = '0',
KEY_1 = '1',
KEY_2 = '2',
KEY_3 = '3',
KEY_4 = '4',
KEY_5 = '5',
KEY_6 = '6',
KEY_7 = '7',
KEY_8 = '8',
KEY_9 = '9',
KEY_COLON = ':',
KEY_SEMICOLON = ';',
KEY_LESS = '<',
KEY_EQUALS = '=',
KEY_GREATER = '>',
KEY_QUESTION = '?',
KEY_AT = '@',
KEY_LEFT_BRACKET = '[',
KEY_RIGHT_BRACKET = ']',
KEY_BACKSLASH = '\\',
KEY_CARET = '^',
KEY_UNDERSCORE = '_',
KEY_BACKQUOTE = '`',
KEY_A = 'A',
KEY_B = 'B',
KEY_C = 'C',
KEY_D = 'D',
KEY_E = 'E',
KEY_F = 'F',
KEY_G = 'G',
KEY_H = 'H',
KEY_I = 'I',
KEY_J = 'J',
KEY_K = 'K',
KEY_L = 'L',
KEY_M = 'M',
KEY_N = 'N',
KEY_O = 'O',
KEY_P = 'P',
KEY_Q = 'Q',
KEY_R = 'R',
KEY_S = 'S',
KEY_T = 'T',
KEY_U = 'U',
KEY_V = 'V',
KEY_W = 'W',
KEY_X = 'X',
KEY_Y = 'Y',
KEY_Z = 'Z',
KEY_CAPSLOCK = 1000,
KEY_F1,
KEY_F2,
KEY_F3,
KEY_F4,
KEY_F5,
KEY_F6,
KEY_F7,
KEY_F8,
KEY_F9,
KEY_F10,
KEY_F11,
KEY_F12,
KEY_PRINTSCREEN,
KEY_SCROLLLOCK,
KEY_PAUSE,
KEY_INSERT,
KEY_DELETE,
KEY_HOME,
KEY_END,
KEY_PAGEUP,
KEY_PAGEDOWN,
KEY_ARROW_UP,
KEY_ARROW_DOWN,
KEY_ARROW_LEFT,
KEY_ARROW_RIGHT,
KEY_NUMLOCK,
KEY_PAD_DIVIDE,
KEY_PAD_MULTIPLY,
KEY_PAD_MINUS,
KEY_PAD_PLUS,
KEY_PAD_ENTER,
KEY_PAD_1,
KEY_PAD_2,
KEY_PAD_3,
KEY_PAD_4,
KEY_PAD_5,
KEY_PAD_6,
KEY_PAD_7,
KEY_PAD_8,
KEY_PAD_9,
KEY_PAD_0,
KEY_PAD_PERIOD,
KEY_LEFT_CTRL,
KEY_RIGHT_CTRL,
KEY_LEFT_SHIFT,
KEY_RIGHT_SHIFT,
KEY_LEFT_ALT,
KEY_RIGHT_ALT,
KEY_MOUSE_LEFT,
KEY_MOUSE_MIDDLE,
KEY_MOUSE_RIGHT,
KEY_MOUSE_WHEEL_UP,
KEY_MOUSE_WHEEL_DOWN,
KEY_MOUSE_4,
KEY_MOUSE_5,
// @Correctness: check for more keys in: USB spec, linux libinput/x11/wayland/whatever, windows win32
};
struct Event_Key
{
bool pressed;
Key_Code key_code;
};
struct Event_Mouse_Move
{
bool relative;
union
{
v2 position;
v2 delta;
};
};
struct Event_Resize
{
s32 width, height;
};
struct Event_Text
{
char data[16]; // @Correctness: We have a bug if the os sends text that is longer
};
struct Event
{
Event_Type type;
union
{
Event_Key key;
Event_Mouse_Move mouse_move;
Event_Resize resize;
Event_Text text;
};
};
#endif

319
code/lib/geometry.h Normal file
View File

@@ -0,0 +1,319 @@
#ifndef _PIUMA_LIB_GEOMETRY_H_
#define _PIUMA_LIB_GEOMETRY_H_
#include "types.h"
#include "math.h"
// Rect
union Rect
{
struct
{
f32 x, y;
f32 w, h;
};
struct
{
v2 position; // Usually, the top-left corner of the rectangle
v2 size;
};
};
inline bool is_inside(Rect rect, v2 p)
{
bool in_range_x = rect.x <= p.x && p.x <= (rect.x + rect.w);
bool in_range_y = rect.y <= p.y && p.y <= (rect.y + rect.h);
return in_range_x && in_range_y;
}
// @Feature: add Cube/Parallelepiped? Unlike Box, it has rotations
// Box
struct Box
{
v3 min;
v3 max;
};
inline bool is_inside(Box b, v3 p)
{
return
(p.x < b.min.x || p.x > b.max.x) ||
(p.y < b.min.y || p.y > b.max.y) ||
(p.z < b.min.z || p.z > b.max.z);
}
inline bool overlaps(Box a, Box b)
{
if(a.min.x > b.max.x) // no overlap
return false;
if(a.max.x < b.min.x) // no overlap
return false;
if(a.min.y > b.max.y) // no overlap
return false;
if(a.max.y < b.min.y) // no overlap
return false;
if(a.min.z > b.max.z) // no overlap
return false;
if(a.max.z < b.min.z) // no overlap
return false;
return true;
}
inline Box box_from_point_cloud(v3 *points, u32 count)
{
if(count == 0)
return Box{.min = {0,0,0}, .max = {0,0,0}};
Box box;
box.min = points[0];
box.max = points[0];
for(u32 i = 0; i < count; i++)
{
v3 p = points[i];
box.min.x = minimum(box.min.x, p.x);
box.min.y = minimum(box.min.y, p.y);
box.min.z = minimum(box.min.z, p.z);
box.max.x = maximum(box.max.x, p.x);
box.max.y = maximum(box.max.y, p.y);
box.max.z = maximum(box.max.z, p.z);
}
return box;
}
inline v3 box_closest_point(Box b, v3 point);
inline f32 box_SDF(Box b, v3 point, v3* res_closest = NULL)
{
v3 closest = box_closest_point(b, point);
f32 sign = -1 * is_inside(b, point);
if(res_closest)
*res_closest = closest;
return sign * distance(closest, point);
}
// Ray
struct Ray
{
v3 position;
v3 direction;
};
// Circle
// @Cleanup: Should Circle be merged with Sphere?
struct Circle
{
v2 center;
f32 radius;
};
inline bool is_inside(Circle c, v2 p)
{
v2 v = p - c.center;
return dot(v, v) <= square(c.radius);
}
// Sphere
struct Sphere
{
v3 center;
f32 radius;
};
inline bool is_inside(Sphere s, v3 p)
{
v3 v = p - s.center;
return dot(v, v) <= square(s.radius); // distance² <= radius²
}
inline f32 sphere_SDF(Sphere s, v3 point)
{
return distance(s.center, point) - s.radius;
}
// Segment
struct Segment
{
v3 a;
v3 b;
};
// Plane
struct Plane
{
v3 normal;
f32 offset;
};
inline f32 plane_SDF(Plane plane, v3 point)
{
f32 projected = dot(point, plane.normal);
return plane.offset - projected;
}
// Projections
inline f32 project_point_on_vector(v3 vector, v3 point)
{
return dot(vector, point);
}
inline v3 project_point_on_plane(Plane plane, v3 point)
{
f32 distance = plane_SDF(plane, point);
return point - plane.normal * distance;
}
// Closest point
inline v3 segment_closest_point(Segment seg, v3 point)
{
v3 ab = seg.b - seg.a;
v3 ap = point - seg.a;
f32 u = dot(ab, ap);
return seg.a + ab * clamp(0.0, 1.0, u);
}
inline v3 box_closest_point(Box b, v3 point)
{
v3 closest;
// Closest point on the box is the one with coords closer to the ones of the point,
// so for each axis we can clamp to the nearest point of the border.
f32 dx1 = abs(b.min.x - point.x);
f32 dx2 = abs(b.max.x - point.x);
closest.x = (dx1 < dx2) ? b.min.x : b.max.x;
f32 dy1 = abs(b.min.y - point.y);
f32 dy2 = abs(b.max.y - point.y);
closest.y = (dy1 < dy2) ? b.min.y : b.max.y;
f32 dz1 = abs(b.min.z - point.z);
f32 dz2 = abs(b.max.z - point.z);
closest.z = (dz1 < dz2) ? b.min.z : b.max.z;
return closest;
}
// Triangles functions
inline v3 triangle_normal(v3 a, v3 b, v3 c)
{
v3 ba = b - a;
v3 ca = c - a;
v3 orthogonal = cross(ba, ca);
return normalize(orthogonal);
}
// Transformations (all angles in radians)
inline m4 rotation_x(f32 angle)
{
f32 c = cos(angle);
f32 s = sin(angle);
m4 result = m4_identity;
result.E[1][1] = c;
result.E[1][2] = -s;
result.E[2][1] = s;
result.E[2][2] = c;
return result;
}
inline m4 rotation_y(f32 angle)
{
f32 c = cos(angle);
f32 s = sin(angle);
m4 result = m4_identity;
result.E[0][0] = c;
result.E[0][2] = s;
result.E[2][0] = -s;
result.E[2][2] = c;
return result;
}
inline m4 rotation_z(f32 angle)
{
f32 c = cos(angle);
f32 s = sin(angle);
m4 result = m4_identity;
result.E[0][0] = c;
result.E[0][1] = -s;
result.E[1][0] = s;
result.E[1][1] = c;
return result;
}
inline m4 rotation(f32 x, f32 y, f32 z)
{
return rotation_z(z) * rotation_y(y) * rotation_x(x);
}
inline m4 rotation_v3(v3 angle)
{
return rotation(angle.x, angle.y, angle.z);
}
inline m4 translation(f32 x, f32 y, f32 z)
{
m4 result = m4_identity;
result.E[0][3] = x;
result.E[1][3] = y;
result.E[2][3] = z;
return result;
}
inline m4 translation_v3(v3 t)
{
return translation(t.x, t.y, t.z);
}
inline m4 scale(f32 x, f32 y, f32 z)
{
m4 result = m4_zero;
result.E[0][0] = x;
result.E[1][1] = y;
result.E[2][2] = z;
result.E[3][3] = 1.0;
return result;
}
inline m4 scale_v3(v3 factor)
{
return scale(factor.x, factor.y, factor.z);
}
inline m4 scale(f32 factor)
{
return scale(factor, factor, factor);
}
// Primitives
// Pass array of 8 elements to fill with coordinates
inline void build_cube_vertices(v3 *vertices)
{
for(int x = 0; x < 2; x++)
for(int y = 0; y < 2; y++)
for(int z = 0; z < 2; z++)
vertices[x*2*2 + y*2 + z] = v3{.x = x - 0.5f, .y = y - 0.5f, .z = z - 0.5f};
}
#endif

97
code/lib/hashing.h Normal file
View File

@@ -0,0 +1,97 @@
#ifndef _PIUMA_LIB_HASHING_H_
#define _PIUMA_LIB_HASHING_H_
//#include "bits.h"
static const u32 hash_crc32_table[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
/*
// Code to compute the lookup table for CRC32
// table needs 256 spaces
static const u32 hash_crc32_polynomial = 0x04C11DB7;
inline void hash_crc32_make_table(u32 *table)
{
u32 polynomial = bits_reverse(hash_crc32_polynomial);
for(u32 byte = 0; byte <= 0xFF; byte++)
{
u32 crc = byte;
for(u8 bit = 0; bit < 8; bit++)
{
if(crc & 1)
crc = (crc >> 1) ^ polynomial;
else
crc = (crc >> 1);
}
table[byte] = crc;
}
}
*/
inline u32 hash_crc32(const void *data, u64 size, u32 seed = 0xFFFFFFFF)
{
u32 crc = seed;
u8 *d = (u8*)data;
while(size--)
{
crc = hash_crc32_table[(crc & 0xFF) ^ *d] ^ (crc >> 8);
d++;
}
return ~crc;
}
/*
// Simpler code to compute CRC32, one bit at a time.
// The version with the lookup table computes 8 bit at a time.
inline u32 hash_crc32(const void *data, u64 size, u32 seed = 0xFFFFFFFF)
{
u8 *d = (u8*)data;
u32 crc = seed;
while(size--)
{
crc = crc ^ (*d++ << 24);
for(int i = 0; i < 8; i++)
{
if(crc & (1L<<31))
crc = (crc << 1) ^ hash_crc32_polynomial;
else
crc = (crc << 1);
}
}
return bits_endian_swap(~crc);
}
*/
#endif

1432
code/lib/math.h Normal file

File diff suppressed because it is too large Load Diff

10
code/lib/memory.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef _PIUMA_LIB_MEMORY_H_
#define _PIUMA_LIB_MEMORY_H_
#include <stddef.h>
typedef void *(*alloc_t)(size_t size);
typedef void *(*realloc_t)(void *ptr, size_t size);
typedef void (*free_t)(void *ptr);
#endif

93
code/lib/queue.h Normal file
View File

@@ -0,0 +1,93 @@
#ifndef _PIUMA_LIB_QUEUE_H_
#define _PIUMA_LIB_QUEUE_H_
#include "types.h"
#include <assert.h>
struct queue_header
{
u64 start;
u64 size;
u64 capacity;
};
#define QUEUE_HEADER_PTR(queue) ((queue_header *)(((u8*)queue) - sizeof(queue_header)))
#define QUEUE_TYPE(type) type *
#define Queue_Alloc(alloc_func, type, capacity) ((type*) _queue_alloc(alloc_func, sizeof(type), capacity))
#define Queue_Free(free_func, queue) free_func(QUEUE_HEADER_PTR(queue))
#define Queue_Pop(queue) (queue[_queue_pop_index((u8*)queue)])
#define Queue_Push(queue, element) { queue[_queue_at_index((u8*)queue, QUEUE_HEADER_PTR(queue)->size)] = element; _queue_push_fix_indices((u8*)queue); }
#define Queue_At(queue, index) (queue[_queue_at_index((u8*)queue, index)])
#define Queue_Size(queue) _queue_size((u8*)queue)
#define Queue_Capacity(queue) _queue_capacity((u8*)queue)
typedef void * (*alloc_func_t)(u64);
typedef void (*free_func_t)(void *);
inline u8 * _queue_alloc(alloc_func_t alloc_func, u64 sizeof_type, u64 capacity)
{
u8 *data;
queue_header *header;
data = (u8 *)alloc_func(sizeof(queue_header) + sizeof_type * capacity);
header = (queue_header *)data;
header->capacity = capacity;
header->start = 0;
header->size = 0;
return data + sizeof(queue_header);
}
inline u64 _queue_pop_index(u8 *queue)
{
queue_header *header = QUEUE_HEADER_PTR(queue);
assert(header->size > 0);
u64 element_index = header->start;
header->start = (header->start + 1) % header->capacity;
header->size--;
return element_index;
}
inline void _queue_push_fix_indices(u8 *queue)
{
queue_header *header = QUEUE_HEADER_PTR(queue);
header->size++;
if(header->size > header->capacity)
{
// Queue is full. Remove oldest element
header->start = (header->start + 1) % header->capacity;
header->size = header->capacity;
}
}
inline u64 _queue_at_index(u8 *queue, u64 index)
{
queue_header *header = QUEUE_HEADER_PTR(queue);
return (header->start + index) % header->capacity;
}
inline u64 _queue_size(u8 *queue)
{
queue_header *header = QUEUE_HEADER_PTR(queue);
return header->size;
}
inline u64 _queue_capacity(u8 *queue)
{
queue_header *header = QUEUE_HEADER_PTR(queue);
return header->capacity;
}
#endif

196
code/lib/text.cpp Normal file
View File

@@ -0,0 +1,196 @@
#include "text.h"
u64 utf8_codepoint_count(const char *s)
{
u64 count = 0;
while(*s)
{
if(utf8_is_codepoint_start(s))
count++;
s++;
}
return count;
}
bool utf8_is_codepoint_start(const char *s)
{
return (*s & 0b11000000) != 0b10000000;
}
u32 utf8_codepoint_bytes(const char *s)
{
if(!utf8_is_codepoint_start(s))
return 0; // Error: This byte belongs to a previous codepoint
if((*s & 0b10000000) == 0b00000000) //1 byte codepoint
return 1;
else if((*s & 0b11100000) == 0b11000000) //2 bytes codepoint
return 2;
else if((*s & 0b11110000) == 0b11100000) //3 bytes codepoint
return 3;
else if((*s & 0b11111000) == 0b11110000) //4 bytes codepoint
return 4;
return 0;
}
/* If bytes_read returns 0, we either reached the end of the string or there was a decoding error */
utf8_codepoint utf8_extract_codepoint(const char *s, u64 current_index, u32 *bytes_read)
{
s += current_index;
// UTF8:
// First byte: (0xxxxxxx = 1 byte, 110xxxxx = 2 byte, 1110xxxx = 3 byte, 11110xxx = 4 byte)
// Next bytes: 10xxxxxx
// To get a Codepoint: concatenate all the xxxx
utf8_codepoint codepoint = 0;
*bytes_read = 0;
u8 next_bytes = 0;
if(!utf8_is_codepoint_start(s))
{
// Error: This byte belongs to a previous codepoint
return 0;
}
if((*s & 0b10000000) == 0b00000000) //1 byte codepoint
{
codepoint = *s;
next_bytes = 0;
}
else if((*s & 0b11100000) == 0b11000000) //2 bytes codepoint
{
codepoint = (*s & 0b00011111);
next_bytes = 1;
}
else if((*s & 0b11110000) == 0b11100000) //3 bytes codepoint
{
codepoint = (*s & 0b00001111);
next_bytes = 2;
}
else if((*s & 0b11111000) == 0b11110000) //4 bytes codepoint
{
codepoint = (*s & 0b00000111);
next_bytes = 3;
}
for(u8 i = 0; i < next_bytes; i++)
{
s++;
if(*s == 0)
{
// Error: End of string reached before completing codepoint
return 0;
}
if((*s & 0b11000000) != 0b10000000)
{
// Error: Byte prefix does not match with the expected one. Broken codepoint
return 0;
}
codepoint = codepoint << 6;
codepoint |= (*s & 0b00111111);
}
*bytes_read = next_bytes + 1;
return codepoint;
}
u32 utf8_bytes_to_next_valid_codepoint(const char *s, u64 current_index)
{
s += current_index;
u64 bytes = 1;
while(*(s + bytes))
{
if(utf8_is_codepoint_start(s + bytes))
break;
bytes++;
}
return bytes;
}
u32 utf8_bytes_to_prev_valid_codepoint(const char *s, u64 current_index)
{
s += current_index;
u64 bytes = 0;
while(bytes < current_index)
{
bytes++;
if(utf8_is_codepoint_start(s - bytes))
break;
}
return bytes;
}
u64 utf8_from_string(const char *s, u64 *bytes_read, utf8_codepoint *result, u64 result_size)
{
u64 decoded = 0;
bytes_read = 0;
while(*s && decoded < result_size)
{
u32 read = 0;
result[decoded] = utf8_extract_codepoint(s, 0, &read);
if(read == 0)
{
bytes_read = 0;
break;
}
s += read;
bytes_read += read;
decoded++;
}
return decoded;
}
u64 utf8_to_string(utf8_codepoint *codepoints, u64 count, char *result, u64 result_size)
{
result_size--; // Reserve space for zero-terminator
u64 i = 0;
u64 result_i = 0;
for(i = 0; i < count; i++)
{
utf8_codepoint cp = codepoints[i];
if((cp & 0xFFFFFF80) == 0) // 1 byte
{
if(result_i + 1 >= result_size) // Not enought space left
break;
result[result_i++] = cp & 0b01111111;
}
else if((cp & 0xFFFFF800) == 0) // 2 bytes
{
if(result_i + 2 >= result_size) // Not enought space left
break;
result[result_i++] = 0b11000000 | ((cp >> 6) & 0b00011111);
result[result_i++] = 0b10000000 | ((cp ) & 0b00111111);
}
else if((cp & 0xFFFF0000) == 0) // 3 bytes
{
if(result_i + 3 >= result_size) // Not enought space left
break;
result[result_i++] = 0b11100000 | ((cp >> 12) & 0b00001111);
result[result_i++] = 0b10000000 | ((cp >> 6) & 0b00111111);
result[result_i++] = 0b10000000 | ((cp ) & 0b00111111);
}
else if((cp & 0xFFE00000) == 0) // 4 bytes
{
if(result_i + 4 >= result_size) // Not enought space left
break;
result[result_i++] = 0b11110000 | ((cp >> 18) & 0b00000111);
result[result_i++] = 0b10000000 | ((cp >> 12) & 0b00111111);
result[result_i++] = 0b10000000 | ((cp >> 6) & 0b00111111);
result[result_i++] = 0b10000000 | ((cp ) & 0b00111111);
}
else
{
// Invalid codepoint
break;
}
}
result[result_i] = 0;
return i;
}

22
code/lib/text.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef _PIUMA_LIB_TEXT_H_
#define _PIUMA_LIB_TEXT_H_
#include "types.h"
typedef u32 utf8_codepoint;
u64 utf8_codepoint_count(const char *s);
bool utf8_is_codepoint_start(const char *s);
u32 utf8_codepoint_bytes(const char *s);
/* If bytes_read returns 0, we either reached the end of the string or there was a decoding error */
utf8_codepoint utf8_extract_codepoint(const char *s, u64 current_index, u32 *bytes_read);
u32 utf8_bytes_to_next_valid_codepoint(const char *s, u64 current_index);
u32 utf8_bytes_to_prev_valid_codepoint(const char *s, u64 current_index);
/* Returns the number of codepoints read. If bytes_read returns 0, there was a decoding error */
u64 utf8_from_string(const char *s, u64 *bytes_read, utf8_codepoint *result, u64 result_size);
/* Returns the number of codepoints written. */
u64 utf8_to_string(utf8_codepoint *codepoints, u64 count, char *result, u64 result_size);
#endif

42
code/lib/types.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef _PIUMA_LIB_TYPES_H_
#define _PIUMA_LIB_TYPES_H_
#include <stdint.h>
#include <stddef.h>
// Integers
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
typedef int64_t s64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
// Real numbers
typedef float f32;
typedef double f64;
// Binary
typedef u8 b8;
typedef u16 b16;
typedef u32 b32;
typedef u64 b64;
// Buffer
struct Buffer
{
u64 size;
u8 *data;
};
struct String
{
u64 size;
u8 *text;
};
#endif

1092
code/linux_platform.cpp Normal file

File diff suppressed because it is too large Load Diff

12
code/linux_platform.h Normal file
View File

@@ -0,0 +1,12 @@
/*
=== PLATFORM IMPLEMENTATION ===
Here we define the structs that were left incomplete in platform.h
===============================
*/
#include <stdio.h>
struct p_file
{
FILE *handle;
};

315
code/main.cpp Normal file
View File

@@ -0,0 +1,315 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "platform.h"
#include "render/render.h"
#include "enginestate.h"
#include "lib/types.h"
#include "lib/math.h"
#include "gui/gui.h"
#include "debug/logger.h"
#include "debug/log_viewer.h"
#include "gui/layout.h"
#include "stb_image.h"
#include <time.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <NetworkManager.h>
bool process_input(); // Returns true when the program needs to exit
void process_gui();
void app_init();
void app_deinit();
u32 seconds_to_duration_text(char *text, f64 seconds, bool show_millis = false)
{
u32 written = 0;
u32 days = seconds / (24 * 3600);
seconds -= days * (24 * 3600);
u32 hours = seconds / 3600;
seconds -= hours * 3600;
u32 minutes = seconds / 60;
seconds -= minutes * 60;
if(days)
written += sprintf(text + written, "%s%dd", (written ? " " : ""), days);
if(days || hours)
written += sprintf(text + written, "%s%dh", (written ? " " : ""), hours);
if(days || hours || minutes)
written += sprintf(text + written, "%s%dm", (written ? " " : ""), minutes);
if(days || hours || minutes || seconds)
{
if(show_millis)
written += sprintf(text + written, "%s%.3lfs", (written ? " " : ""), seconds);
else
written += sprintf(text + written, "%s%.0lfs", (written ? " " : ""), seconds);
}
return written;
}
void load_image(const char *filename, u8 **data, s32 *width, s32 *height, s32 *channels)
{
stbi_set_flip_vertically_on_load(false);
*data = stbi_load(filename, width, height, channels, 4);
}
int main(int argc, char *argv[])
{
bool status;
LOG_INIT();
p_init(true);
LogViewer log_viewer = LogViewer::Init(&global_logger);
p_window_open();
p_window_name((char*)"Server Monitor");
r_init();
gui_init();
log_viewer.Print_New_Messages_On_Console();
app_init();
f64 start = p_time();
f64 prev_t = 0;
while(1)
{
// Engine
engine.time = p_time() - start;
engine.delta_t = engine.time - prev_t;
//LOG(LOG_INFO, "Frame time: %.3lf ms FPS: %.2lf", 1000*delta_t, 1/delta_t);
r_time_update(engine.time);
// Input
bool exit = process_input();
if(exit)
break;
// GUI
r_framebuffer_select(&r_render_state.framebuffer_SCREEN);
r_clear({0,0,0,0});
gui_frame_begin(engine.time);
process_gui();
gui_frame_end();
log_viewer.Print_New_Messages_On_Console();
r_swap();
prev_t = engine.time;
}
app_deinit();
gui_deinit();
p_window_close();
LOG_DEINIT();
p_deinit();
return 0;
}
bool process_input()
{
// Events
Event event;
while(p_next_event(&event))
{
gui_handle_event(&event);
switch(event.type)
{
case EVENT_QUIT: {
return true;
};
case EVENT_MOUSE_MOVE:
{
} break;
case EVENT_KEY:
{
switch(event.key.key_code)
{
default: {
// Other keys. Put default to suppress warnings
} break;
}
} break;
case EVENT_RESIZE:
{
//LOG(LOG_DEBUG, "New size: %u %u", signal.resize.width, signal.resize.height);
s32 width = event.resize.width;
s32 height = event.resize.height;
r_size_update(width, height);
global_gui_state.default_context.width = width;
global_gui_state.default_context.height = height;
engine.gui_scaling = minimum(width, height) * 0.03;
global_gui_state.default_context.style.font_size = engine.gui_scaling;
global_gui_state.default_context.style.button_radius = engine.gui_scaling / 10;
} break;
case EVENT_FOCUS:
{
} break;
case EVENT_UNFOCUS:
{
} break;
}
}
return false;
}
NMClient *nmclient;
void app_init()
{
nmclient = nm_client_new(NULL, NULL);
}
void app_deinit()
{
}
void system_info_window()
{
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,10}*engine.gui_scaling, 3, 6, 0.4*engine.gui_scaling);
gui_window_start(Rect{0.1*engine.gui_scaling, 0.1*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef01);
// Hostname
struct utsname host_info;
uname(&host_info);
char hostname[128] = "Server manager";
if(host_info.nodename[0])
strcpy(hostname, host_info.nodename);
char kernel[256];
sprintf(kernel, "%s %s", host_info.sysname, host_info.release);
// Clock
time_t time_now = time(NULL);
struct tm *time_info = localtime(&time_now);
char date_string[128];
strftime(date_string, 128, "%a %e %b %Y", time_info);
char time_string[128];
strftime(time_string, 128, "%H:%M:%S %Z", time_info);
gui_text_aligned(layout.cell(), hostname, GUI_ALIGN_LEFT);
gui_text_aligned(layout.cell(), time_string, GUI_ALIGN_CENTER);
gui_text_aligned(layout.cell(), date_string, GUI_ALIGN_RIGHT);
// Load, Memory, Uptime
struct sysinfo sys_info;
sysinfo(&sys_info);
char uptime[128] = "Uptime: "; seconds_to_duration_text(uptime + strlen("Uptime: "), sys_info.uptime);
f32 load_scale = 1.0f / (1 << SI_LOAD_SHIFT);
f32 loads[3] = {
load_scale * sys_info.loads[1],
load_scale * sys_info.loads[1],
load_scale * sys_info.loads[1]
};
for(int i = 0; i < 3; i++)
loads[i] = round(load_scale * sys_info.loads[i] * 100) / 100;
char load[128]; sprintf(load, "Load: %.2f %.2f %.2f", loads[0], loads[1], loads[2]);
int n_processors = get_nprocs();
int n_processors_active = get_nprocs_conf();
char processors[128]; sprintf(processors, "CPUs: %d/%d", n_processors_active, n_processors);
u64 ram_total = sys_info.totalram * sys_info.mem_unit;
u64 ram_used = (sys_info.totalram - sys_info.freeram - sys_info.bufferram) * sys_info.mem_unit;
char ram[128]; sprintf(ram, "RAM: %.2f/%.2f GiB", ram_used / (1024.0*1024.0*1024.0), ram_total / (1024.0*1024.0*1024.0));
layout.row(2);
gui_text_aligned(layout.cell(), processors, GUI_ALIGN_LEFT);
gui_text_aligned(layout.cell(), load, GUI_ALIGN_CENTER);
gui_text_aligned(layout.cell(), uptime, GUI_ALIGN_RIGHT);
gui_text_aligned(layout.cell(), ram, GUI_ALIGN_LEFT);
gui_window_end();
}
void network_window()
{
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,12}*engine.gui_scaling, 3, 7, 0.4*engine.gui_scaling);
gui_window_start(Rect{0.1*engine.gui_scaling, 11*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef02);
const GPtrArray *devices = nm_client_get_devices(nmclient);
for(int i = 0; i < devices->len; i++)
{
NMDevice *device = (NMDevice*)devices->pdata[i];
const char *device_name = nm_device_get_iface(device);
gui_button(layout.cell(), device_name);
Gui_Context *ctx = &global_gui_state.default_context;
gui_id_stack_push(ctx, gui_id_from_pointer(ctx, device_name));
switch(nm_device_get_device_type(device))
{
case NM_DEVICE_TYPE_ETHERNET: gui_button(layout.cell(), "ETHERNET"); break;
case NM_DEVICE_TYPE_WIFI: gui_button(layout.cell(), "WIFI"); break;
case NM_DEVICE_TYPE_TUN: gui_button(layout.cell(), "TAP or TUN"); break;
case NM_DEVICE_TYPE_BRIDGE: gui_button(layout.cell(), "BRIDGE"); break;
case NM_DEVICE_TYPE_VLAN: gui_button(layout.cell(), "VLAN"); break;
case NM_DEVICE_TYPE_WIREGUARD: gui_button(layout.cell(), "WIREGUARD"); break;
default: gui_button(layout.cell(), ""); break;
}
switch(nm_device_get_state(device))
{
case NM_DEVICE_STATE_UNKNOWN: gui_button(layout.cell(), "UNKNOWN"); break;
case NM_DEVICE_STATE_UNMANAGED: gui_button(layout.cell(), "UNMANAGED"); break;
case NM_DEVICE_STATE_UNAVAILABLE: gui_button(layout.cell(), "UNAVAILABLE"); break;
case NM_DEVICE_STATE_DISCONNECTED: gui_button(layout.cell(), "DISCONNECTED"); break;
case NM_DEVICE_STATE_PREPARE: gui_button(layout.cell(), "PREPARE"); break;
case NM_DEVICE_STATE_CONFIG: gui_button(layout.cell(), "CONFIG"); break;
case NM_DEVICE_STATE_NEED_AUTH: gui_button(layout.cell(), "NEED_AUTH"); break;
case NM_DEVICE_STATE_IP_CONFIG: gui_button(layout.cell(), "IP_CONFIG"); break;
case NM_DEVICE_STATE_IP_CHECK: gui_button(layout.cell(), "IP_CHECK"); break;
case NM_DEVICE_STATE_SECONDARIES: gui_button(layout.cell(), "SECONDARIES"); break;
case NM_DEVICE_STATE_ACTIVATED: gui_button(layout.cell(), "ACTIVATED"); break;
case NM_DEVICE_STATE_DEACTIVATING: gui_button(layout.cell(), "DEACTIVATING"); break;
case NM_DEVICE_STATE_FAILED: gui_button(layout.cell(), "FAILED"); break;
default: gui_button(layout.cell(), ""); break;
}
gui_id_stack_pop(ctx);
layout.row();
}
gui_window_end();
}
void vm_window()
{
Gui_Layout_Grid layout = gui_layout_grid_create_by_divisions(v2{0,0}, v2{40,8}*engine.gui_scaling, 3, 7, 0.4*engine.gui_scaling);
gui_window_start(Rect{0.1*engine.gui_scaling, 24*engine.gui_scaling, layout.window_size.x, layout.window_size.y}, 0xabcdef03);
gui_window_end();
}
void process_gui()
{
g_main_context_iteration(NULL, false);
system_info_window();
network_window();
vm_window();
}

17
code/physics/base.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef _PIUMA_PHYSICS_BASE_H_
#define _PIUMA_PHYSICS_BASE_H_
#include "../lib/memory.h"
/* Reassing phy_alloc and phy_free with your own alloc/free functions if you don't
* want to use malloc and free.
*/
extern alloc_t phy_alloc;
extern realloc_t phy_realloc;
extern free_t phy_free;
#define PHY_ALLOC(type, size) (type *)phy_alloc(sizeof(type) * size)
#define PHY_REALLOC(type, ptr, size) (type *)phy_realloc(ptr, sizeof(type) * size)
#define PHY_FREE(ptr) phy_free(ptr)
#endif

28
code/physics/body.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "body.h"
#include "../lib/geometry.h"
#include <assert.h>
Box phy_aabb_from_body(phy_body *body)
{
Box aabb;
switch(body->shape)
{
case PHY_SHAPE_SPHERE: {
aabb.min = body->position - body->sphere.radius * v3{1,1,1};
aabb.max = body->position + body->sphere.radius * v3{1,1,1};
} break;
case PHY_SHAPE_BOX: {
v3 box[8]; build_cube_vertices(box);
m3 rotoscale = M3(rotation_v3(body->rotation) * scale_v3(body->box.dimensions));
for(u32 i = 0; i < 8; i++)
box[i] = body->position + rotoscale * box[i];
aabb = box_from_point_cloud(box, 8);
} break;
case PHY_SHAPE_MESH: {
assert(false && "Yet to be implemented");
} break;
default: { assert(false && "Unknown shape"); }
}
return aabb;
}

61
code/physics/body.h Normal file
View File

@@ -0,0 +1,61 @@
#ifndef _PIUMA_PHYSICS_OBJECT_H_
#define _PIUMA_PHYSICS_OBJECT_H_
#include "../lib/math.h"
#include "../lib/geometry.h"
enum phy_shape_type
{
PHY_SHAPE_SPHERE,
PHY_SHAPE_BOX,
PHY_SHAPE_MESH,
PHY_SHAPE_COUNT
};
struct phy_sphere
{
f32 radius;
};
struct phy_box
{
v3 dimensions;
};
struct phy_mesh
{
// TODO
};
struct phy_body
{
v3 position;
v3 rotation;
f32 mass;
v3 center_of_mass;
f32 gravity_multiplier;
f32 friction;
f32 bounciness;
// Dynamics
v3 velocity;
v3 angular_velocity;
// Collision
phy_shape_type shape;
union
{
phy_sphere sphere;
phy_box box;
phy_mesh mesh;
};
Box aabb;
};
Box phy_aabb_from_body(phy_body *body);
#endif

336
code/physics/collision.cpp Normal file
View File

@@ -0,0 +1,336 @@
#include "collision.h"
#include "../lib/ds.h"
#include "../lib/geometry.h"
#include <assert.h>
void phy_collisions_broadphase(phy_world *world, phy_body_pair *pair_list, u32 max_pairs, u32 *num_pairs)
{
// Update AABBs
for(u32 i = 0; i < world->bodies_count; i++)
{
phy_body *body = world->bodies + i;
body->aabb = phy_aabb_from_body(body);
}
u32 pairs_count = 0;
for(u32 a = 0; a < world->bodies_count; a++)
{
phy_body *body_a = world->bodies + a;
for(u32 b = a + 1; b < world->bodies_count; b++)
{
phy_body *body_b = world->bodies + b;
if(overlaps(body_a->aabb, body_b->aabb))
{
// Add to list of possible collisions.
if(pairs_count < max_pairs)
{
pair_list[pairs_count] = phy_body_pair{
.body_a = body_a,
.body_b = body_b
};
}
pairs_count++;
}
}
}
if(num_pairs)
*num_pairs = pairs_count;
}
// @Cleanup: put this in the right place
// @Performance: this is probably not a good way of doing this
void project_box_on_axis(phy_body *body, v3 axis, f32 *min, f32 *max)
{
v3 points[8];
build_cube_vertices(points);
m4 transform = translation_v3(body->position) * rotation_v3(body->rotation) * scale_v3(body->box.dimensions);
*min = *max = project_point_on_vector(axis, body->position);
for(int i = 0; i < 8; i++)
{
f32 projected = project_point_on_vector(axis, (transform * V4(points[i], 1)).xyz);
*min = minimum(*min, projected);
*max = maximum(*max, projected);
}
}
f32 axis_range_distance(f32 a_min, f32 a_max, f32 b_min, f32 b_max)
{
f32 a_range = a_max - a_min;
f32 a_center = a_min + a_range / 2;
f32 b_range = b_max - b_min;
f32 b_center = b_min + b_range / 2;
return fabs(a_center - b_center) - (a_range + b_range) / 2;
}
bool sat_box_face_point_collision_test(phy_body *body_a, phy_body *body_b, v3 axis, f32 a_size_on_current_axis, f32 *depth, v3 *furthest_a, v3 *furthest_b)
{
f32 a_middle = project_point_on_vector(axis, body_a->position);
f32 b_middle = project_point_on_vector(axis, body_b->position);
f32 direction = (b_middle - a_middle < 0) ? -1 : 1;
f32 boundary = a_middle + direction * a_size_on_current_axis / 2;
f32 dist = +INFINITY;
v3 point;
v3 points[8]; build_cube_vertices(points);
m4 transform = translation_v3(body_b->position) * rotation_v3(body_b->rotation) * scale_v3(body_b->box.dimensions);
for(int i = 0; i < 8; i++)
{
v3 p = (transform * V4(points[i], 1)).xyz;
f32 projected = project_point_on_vector(axis, p);
f32 curr_dist = direction * (projected - boundary);
if(curr_dist < dist)
{
dist = curr_dist;
point = p;
}
}
if(dist < 0) // Possible collision
{
v3 furthest_point_b = point;
v3 furthest_point_a = point + -dist * -direction * axis;
*depth = -dist * direction;
*furthest_a = furthest_point_a;
*furthest_b = furthest_point_b;
return true;
}
// else Separated on this axis. No collision
return false;
}
bool sat_box_collision_test(phy_body *body_a, phy_body *body_b, phy_collision *res_collision)
{
// Separating axis test
// 3 + 3 = 6 face normals
// 3 * 3 = 9 right angle to pair of edges
v3 axes[15];
m4 a_rotation = rotation_v3(body_a->rotation);
axes[0] = extract_column(a_rotation, 0).xyz;
axes[1] = extract_column(a_rotation, 1).xyz;
axes[2] = extract_column(a_rotation, 2).xyz;
m4 b_rotation = rotation_v3(body_b->rotation);
axes[3] = -extract_column(b_rotation, 0).xyz;
axes[4] = -extract_column(b_rotation, 1).xyz;
axes[5] = -extract_column(b_rotation, 2).xyz;
for(int a = 0; a < 3; a++)
for(int b = 0; b < 3; b++)
{
(axes+6)[3*a + b] = cross(axes[a], axes[b + 3]);
}
phy_collision collision;
collision.body_a = body_a;
collision.body_b = body_b;
collision.depth = +INFINITY;
// Face axes: body a
for(int i = 0; i < 3; i++)
{
v3 axis = axes[i];
f32 depth; v3 furthest_point_a, furthest_point_b;
bool collides = sat_box_face_point_collision_test(body_a, body_b, axis, body_a->box.dimensions.E[i], &depth, &furthest_point_a, &furthest_point_b);
if(!collides)
return false;
if(abs(depth) < abs(collision.depth))
{
collision.depth = depth;
collision.furthest_point_a = furthest_point_a;
collision.furthest_point_b = furthest_point_b;
collision.best_separation_direction = axis;
}
}
// Face axes: body b
for(int i = 0; i < 3; i++)
{
v3 axis = axes[3 + i];
f32 depth; v3 furthest_point_a, furthest_point_b;
bool collides = sat_box_face_point_collision_test(body_b, body_a, axis, body_b->box.dimensions.E[i], &depth, &furthest_point_b, &furthest_point_a);
if(!collides)
return false;
if(abs(depth) < abs(collision.depth))
{
collision.depth = depth;
collision.furthest_point_a = furthest_point_a;
collision.furthest_point_b = furthest_point_b;
collision.best_separation_direction = -axis;
}
}
// @TODO: edge-edge
*res_collision = collision;
return true;
}
void phy_collisions_detection(phy_world *world, phy_body_pair *pair_list, u32 num_pairs, phy_collision *collision_list, u32 max_collisions, u32 *num_collisions)
{
u32 n_collisions = 0;
for(u32 i = 0; i < num_pairs; i++)
{
phy_body *body_a = pair_list[i].body_a;
phy_body *body_b = pair_list[i].body_b;
if(body_b->shape < body_a->shape)
swap(body_b, body_a);
switch(body_a->shape)
{
case PHY_SHAPE_SPHERE:
{
switch(body_b->shape)
{
case PHY_SHAPE_SPHERE:
{
f32 dist = distance(body_a->position, body_b->position);
f32 radius_sum = body_a->sphere.radius + body_b->sphere.radius;
if(dist <= radius_sum)
{
phy_collision collision;
collision.body_a = body_a;
collision.body_b = body_b;
collision.depth = radius_sum - dist;
v3 direction = normalize(body_b->position - body_a->position);
collision.furthest_point_a = body_a->position + (body_a->sphere.radius - collision.depth) * direction;
collision.furthest_point_b = body_b->position + (body_b->sphere.radius - collision.depth) * -direction;
collision.best_separation_direction = direction;
if(n_collisions < max_collisions)
collision_list[n_collisions] = collision;
n_collisions++;
}
} break;
case PHY_SHAPE_BOX:
{
// Use box's cordinate space
m4 box_b_coord_space_transform = rotation_v3(-body_b->rotation) * translation_v3(-body_b->position);
v4 sphere_center4 = box_b_coord_space_transform * V4(body_a->position, 1.0);
v3 sphere_center = sphere_center4.xyz / sphere_center4.w;
Box box = {
.min = -0.5*body_b->box.dimensions,
.max = 0.5*body_b->box.dimensions
};
v3 closest_point;
f32 dist = box_SDF(box, sphere_center, &closest_point);
if(dist - body_a->sphere.radius <= 0)
{
phy_collision collision;
collision.body_a = body_a;
collision.body_b = body_b;
collision.depth = dist - body_a->sphere.radius;
v3 direction = normalize(closest_point - body_a ->position);
collision.furthest_point_a = body_a->position + (body_a->sphere.radius - collision.depth) * direction;
collision.furthest_point_b = body_a->position + body_a->sphere.radius * direction;
collision.best_separation_direction = direction;
if(n_collisions < max_collisions)
collision_list[n_collisions] = collision;
n_collisions++;
}
} break;
case PHY_SHAPE_MESH:
{
assert(false && "Unmanaged PHY_SHAPE pair");
} break;
default:
{
assert(false && "Unmanaged PHY_SHAPE pair");
} break;
}
} break;
case PHY_SHAPE_BOX:
{
switch(body_b->shape)
{
// case PHY_SHAPE_SPHERE: // Already managed
case PHY_SHAPE_BOX:
{
phy_collision collision;
bool collides = sat_box_collision_test(body_a, body_b, &collision);
if(collides)
{
if(n_collisions < max_collisions)
collision_list[n_collisions] = collision;
n_collisions++;
}
} break;
case PHY_SHAPE_MESH:
{
assert(false && "Unmanaged PHY_SHAPE pair");
} break;
default:
{
assert(false && "Unmanaged PHY_SHAPE pair");
} break;
}
} break;
case PHY_SHAPE_MESH:
{
switch(body_b->shape)
{
// case PHY_SHAPE_SPHERE: // Already managed
// case PHY_SHAPE_BOX: // Already managed
case PHY_SHAPE_MESH:
{
assert(false && "Unmanaged PHY_SHAPE pair");
} break;
default:
{
assert(false && "Unmanaged PHY_SHAPE pair");
} break;
}
} break;
default:
{
assert(false && "Unmanaged PHY_SHAPE");
} break;
}
}
*num_collisions = n_collisions;
}
void phy_collisions_resolution(phy_world *world, phy_collision *collision_list, u32 num_collisions)
{
for(u32 i = 0; i < num_collisions; i++)
{
phy_body *body_a = collision_list[i].body_a;
phy_body *body_b = collision_list[i].body_b;
v3 separation_direction = collision_list[i].best_separation_direction;
f32 depth = collision_list[i].depth;
if(body_a->mass != 0) // Not a static body
{
body_a->position += -separation_direction * depth;//-normalize(body_a->velocity) * dot(normalize(body_a->velocity), separation_direction * depth);
body_a->velocity = {0,0,0};//length(body_a->velocity) * separation_direction * body_a->bounciness;
}
if(body_b->mass != 0) // Not a static body
{
body_b->position += separation_direction * depth;//-normalize(body_b->velocity) * dot(normalize(body_b->velocity), separation_direction * depth);
body_b->velocity = {0,0,0};//length(body_a->velocity) * -separation_direction * body_b->bounciness;
}
}
}

35
code/physics/collision.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef _PIUMA_PHYSICS_COLLISION_H_
#define _PIUMA_PHYSICS_COLLISION_H_
#include "world.h"
struct phy_body_pair
{
phy_body *body_a;
phy_body *body_b;
};
struct phy_collision
{
phy_body *body_a;
phy_body *body_b;
f32 depth;
v3 furthest_point_a;
v3 furthest_point_b;
v3 best_separation_direction;
};
// Generates possibile collision pairs and saves it in the memory pointed by pair_list, up to a maximum of max_pairs. The number of possible collisions is returned in num_pairs (might be >= max_pairs).
void phy_collisions_broadphase(phy_world *world, phy_body_pair *pair_list, u32 max_pairs, u32 *num_pairs);
// Generates list of collisions and saves it in the memory pointed by collision_list, up to a maximum of max_pairs. The number of collisions is returned in num_collisions (might be >= max_collisions).
// Uses pair_list (with size num_pairs) as a list of possible collisions (computed with a faster algorithm).
void phy_collisions_detection(phy_world *world, phy_body_pair *pair_list, u32 num_pairs, phy_collision *collision_list, u32 max_collisions, u32 *num_collisions);
// Modifies world state based on collision_list
void phy_collisions_resolution(phy_world *world, phy_collision *collision_list, u32 num_collisions);
#endif

25
code/physics/physics.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include "physics.h"
#include <stdlib.h>
alloc_t phy_alloc = malloc;
realloc_t phy_realloc = realloc;
free_t phy_free = free;
void phy_init()
{
phy_init(malloc, realloc, free);
}
void phy_init(alloc_t alloc, realloc_t realloc, free_t free)
{
phy_alloc = alloc;
phy_realloc = realloc;
phy_free = free;
}
void phy_deinit()
{
}

16
code/physics/physics.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef _PIUMA_PHYSICS_PHYSICS_H_
#define _PIUMA_PHYSICS_PHYSICS_H_
#include "base.h"
#include "body.h"
#include "world.h"
#include "simulation.h"
#include "collision.h"
// Initialization
void phy_init();
void phy_init(alloc_t alloc, realloc_t realloc, free_t free);
void phy_deinit();
#endif

View File

@@ -0,0 +1,40 @@
#include "base.h"
#include "simulation.h"
#include "../lib/geometry.h"
#include "collision.h"
void phy_integrate(phy_world *world, f64 delta_t)
{
for(u32 i = 0; i < world->bodies_count; i++)
{
phy_body *body = world->bodies + i;
body->velocity += world->gravity * body->gravity_multiplier * delta_t;
body->position += body->velocity * delta_t;
body->rotation += body->angular_velocity * delta_t;
}
}
void phy_simulate(phy_world *world, f64 delta_t)
{
// @Performance: Grouping for optimizations (active/inactive, constraints)
phy_integrate(world, delta_t);
u32 max_pairs = 1000; // @Correctness: this should be dynamic and should reuse memory when possible
u32 num_pairs = 0;
phy_body_pair *pair_list = PHY_ALLOC(phy_body_pair, max_pairs);
phy_collisions_broadphase(world, pair_list, max_pairs, &num_pairs);
u32 max_collisions = 1000;
u32 num_collisions = 0;
phy_collision *collision_list = PHY_ALLOC(phy_collision, max_collisions);
phy_collisions_detection(world, pair_list, minimum(max_pairs, num_pairs), collision_list, max_collisions, &num_collisions);
phy_collisions_resolution(world, collision_list, minimum(max_collisions, num_collisions));
PHY_FREE(pair_list);
PHY_FREE(collision_list);
}

View File

@@ -0,0 +1,9 @@
#ifndef _PIUMA_PHYSICS_SIMULATION_H_
#define _PIUMA_PHYSICS_SIMULATION_H_
#include "world.h"
void phy_integrate(phy_world *world, f64 delta_t);
void phy_simulate(phy_world *world, f64 delta_t);
#endif

72
code/physics/world.cpp Normal file
View File

@@ -0,0 +1,72 @@
#include "world.h"
#include "base.h"
phy_world *phy_create_world(v3 gravity)
{
phy_world *world = PHY_ALLOC(phy_world, 1);
world->bodies = NULL;
world->bodies_count = 0;
world->gravity = gravity;
return world;
}
void phy_destroy_world(phy_world *world)
{
PHY_FREE(world->bodies);
world->bodies_count = 0;
}
phy_body_offset phy_new_body(phy_world *world)
{
phy_body_offset offset = world->bodies_count;
world->bodies_count++;
world->bodies = PHY_REALLOC(phy_body, world->bodies, world->bodies_count);
return offset;
}
void phy_body_init_default(phy_body *body)
{
body->position = {0, 0, 0};
body->rotation = {0, 0, 0};
body->mass = 0;
body->center_of_mass = {0, 0, 0};
body->velocity = {0, 0, 0};
body->angular_velocity = {0, 0, 0};
body->gravity_multiplier = 1;
body->friction = 0;
body->bounciness = 1;
}
phy_body_offset phy_create_box(phy_world *world, v3 position, v3 rotation, f32 mass, v3 dimensions)
{
phy_body_offset offset = phy_new_body(world);
phy_body *body = PHY_BODY(world, offset);
phy_body_init_default(body);
body->position = position;
body->rotation = rotation;
body->mass = mass;
body->shape = PHY_SHAPE_BOX;
body->box.dimensions = dimensions;
return offset;
}
phy_body_offset phy_create_sphere(phy_world *world, v3 position, v3 rotation, f32 mass, f32 radius)
{
phy_body_offset offset = phy_new_body(world);
phy_body *body = PHY_BODY(world, offset);
phy_body_init_default(body);
body->position = position;
body->rotation = rotation;
body->mass = mass;
body->shape = PHY_SHAPE_SPHERE;
body->sphere.radius = radius;
return offset;
}

23
code/physics/world.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef _PIUMA_PHYSICS_WORLD_H_
#define _PIUMA_PHYSICS_WORLD_H_
#include "body.h"
struct phy_world
{
phy_body *bodies;
u32 bodies_count;
v3 gravity;
};
typedef u32 phy_body_offset;
#define PHY_BODY(world, offset) (world->bodies + offset)
phy_world *phy_create_world(v3 gravity);
void phy_destroy_world(phy_world *world);
phy_body_offset phy_create_box(phy_world *world, v3 position, v3 rotation, f32 mass, v3 dimensions);
phy_body_offset phy_create_sphere(phy_world *world, v3 position, v3 rotation, f32 mass, f32 radius);
#endif

92
code/platform.h Normal file
View File

@@ -0,0 +1,92 @@
#ifndef _PIUMA_PLATFORM_H_
#define _PIUMA_PLATFORM_H_
/*
=== PLATFORM INTERFACE ===
Every function or struct must be implemented in the code for each platform
==========================
*/
#include "lib/types.h"
#include "lib/event.h"
// @Feature: Initialization structures with preferred initial state (window resolution, fullcreen)
// Generic platform initialization
void p_init(bool capture_os_signals = false);
void p_deinit();
// Memory
void * p_alloc(u64 size);
void * p_realloc(void *ptr, u64 new_size);
void p_free(void *ptr);
// File IO
#define P_FILE_CREATE_IF_NOT_EXISTS 1
struct p_file; // Defined by specific platform implementation
bool p_file_init(p_file *file, const char *filename, b32 flags = 0);
u64 p_file_size(p_file *file);
bool p_file_read(p_file *file, Buffer *buf, u64 max_size);
bool p_file_write(p_file *file, u8 *data, u64 size);
// @Performance: add append + write at position. Maybe not needed
void p_file_deinit(p_file *file);
// Timers
f64 p_time(); // Returns seconds
void p_wait(f64 milliseconds);
// Windowing
// We suppose we only need 1 window. Every GL command will render to it.
void p_window_open();
void p_window_name(char *name);
void p_window_resize(u32 width, u32 height, bool fullscreen = false);
void p_window_dimensions(u32 *width, u32 *height);
void p_window_close();
// Graphics
void p_graphics_swap_interval(s32 frames); // -1 for Adaptive Sync, 0 for no interval, >0 to wait 'frames' frames between each swap
void p_graphics_swap();
// Input (keyboard / mouse / controller)
void p_mouse_grab(bool grab);
// Events
bool p_next_event(Event *e);
// Audio
struct p_audio_buffer;
typedef void (*p_audio_callback)(p_audio_buffer *);
void p_audio_register_data_callback(p_audio_callback cb);
u32 p_audio_sample_rate();
// Threads
// @Feature: OS threads and syncronization
// Audio structs
struct p_audio_sample
{
f32 left;
f32 right;
};
struct p_audio_buffer
{
p_audio_sample *samples;
u64 size;
};
#if defined(__linux__)
#include "linux_platform.h"
#else
#error "Platform not supported"
#endif
#endif

382
code/render/2d.cpp Normal file
View File

@@ -0,0 +1,382 @@
#include "2d.h"
#include "state.h"
#include "../debug/logger.h"
static const u32 PIXELS_PER_SEGMENT = 3;
// Internal use functions
static void set_texture_in_shader(r_shader *shader, r_texture *texture, v4 color = {1,1,1,1}, u32 texture_index = 0);
// Immediate functions
void r_2d_immediate_segment(v2 a, v2 b, v4 a_color, v4 b_color, f32 thickness)
{
glDisable(GL_DEPTH_TEST);
// Shader
glUseProgram(r_render_state.shader_2d.id);
glUniform1i(r_render_state.shader_2d.has_texture[0], 0);
// Vertex buffer data
GLuint gl_VAO, gl_VBO;
glGenVertexArrays(1, &gl_VAO);
glBindVertexArray(gl_VAO);
glGenBuffers(1, &gl_VBO);
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
f32 data[12] = { a.x, a.y, b.x, b.y, a_color.r, a_color.g, a_color.b, a_color.a, b_color.r, b_color.g, b_color.b, b_color.a };
glBufferData(GL_ARRAY_BUFFER, 2*sizeof(v2)+2*sizeof(v4), data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)(2*sizeof(v2)));
// Draw
glLineWidth(thickness);
glDrawArrays(GL_LINES, 0, 2);
// Deinit
glDeleteBuffers(1, &gl_VBO);
glDeleteVertexArrays(1, &gl_VAO);
glEnable(GL_DEPTH_TEST);
}
void r_2d_immediate_polygonal_chain(u64 count, v2 *vertices, v4 color, f32 thickness)
{
glDisable(GL_DEPTH_TEST);
// Shader
glUseProgram(r_render_state.shader_2d.id);
glUniform1i(r_render_state.shader_2d.has_texture[0], 0);
// Vertex buffer data
GLuint gl_VAO, gl_VBO;
glGenVertexArrays(1, &gl_VAO);
glBindVertexArray(gl_VAO);
glGenBuffers(1, &gl_VBO);
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
glBufferData(GL_ARRAY_BUFFER, count*sizeof(v2), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
//glDisableVertexAttribArray(1);
glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
// Draw
glLineWidth(thickness);
glDrawArrays(GL_LINE_STRIP, 0, count);
// Deinit
glDeleteBuffers(1, &gl_VBO);
glDeleteVertexArrays(1, &gl_VAO);
glEnable(GL_DEPTH_TEST);
}
void r_2d_immediate_triangle(v2 a, v2 b, v2 c, v4 a_color, v4 b_color, v4 c_color, v2 a_uv, v2 b_uv, v2 c_uv, r_texture *texture)
{
glDisable(GL_DEPTH_TEST);
// Shader
glUseProgram(r_render_state.shader_2d.id);
// Texture
set_texture_in_shader(&r_render_state.shader_2d, texture);
// Vertex buffer data
GLuint gl_VAO, gl_VBO;
glGenVertexArrays(1, &gl_VAO);
glBindVertexArray(gl_VAO);
glGenBuffers(1, &gl_VBO);
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
f32 data[24] = {
a.x, a.y, b.x, b.y, c.x, c.y,
a_color.r, a_color.g, a_color.b, a_color.a,
b_color.r, b_color.g, b_color.b, b_color.a,
c_color.r, c_color.g, c_color.b, c_color.a,
a_uv.x, a_uv.y, b_uv.x, b_uv.y, c_uv.x, c_uv.y
};
glBufferData(GL_ARRAY_BUFFER, 3*sizeof(v2)+3*sizeof(v4)+3*sizeof(v2), data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)(3*sizeof(v2)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)(3*sizeof(v2)+3*sizeof(v4)));
// Draw
glDrawArrays(GL_TRIANGLES, 0, 3);
// Deinit
glDeleteBuffers(1, &gl_VBO);
glDeleteVertexArrays(1, &gl_VAO);
glEnable(GL_DEPTH_TEST);
}
void r_2d_immediate_quad(v2 a, v2 b, v2 c, v2 d, v4 color, v2 a_uv, v2 b_uv, v2 c_uv, v2 d_uv, r_texture *texture)
{
glDisable(GL_DEPTH_TEST);
// Shader
glUseProgram(r_render_state.shader_2d.id);
// Texture
set_texture_in_shader(&r_render_state.shader_2d, texture);
// Vertex buffer data
GLuint gl_VAO, gl_VBO;
glGenVertexArrays(1, &gl_VAO);
glBindVertexArray(gl_VAO);
glGenBuffers(1, &gl_VBO);
glBindBuffer(GL_ARRAY_BUFFER, gl_VBO);
f32 data[16] = {
a.x, a.y, b.x, b.y, d.x, d.y, c.x, c.y,
a_uv.x, a_uv.y, b_uv.x, b_uv.y, d_uv.x, d_uv.y, c_uv.x, c_uv.y
};
glBufferData(GL_ARRAY_BUFFER, 4*sizeof(v2)+4*sizeof(v2), data, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
//glDisableVertexAttribArray(1);
glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)(4*sizeof(v2)));
// Draw
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// Deinit
glDeleteBuffers(1, &gl_VBO);
glDeleteVertexArrays(1, &gl_VAO);
glEnable(GL_DEPTH_TEST);
}
void r_2d_immediate_rectangle(Rect r, v4 color, Rect uv, r_texture *texture)
{
v2 a = r.position + v2{r.w, 0 };
v2 b = r.position + v2{0 , 0 };
v2 c = r.position + v2{0 , r.h};
v2 d = r.position + v2{r.w, r.h};
v2 a_uv = uv.position + v2{uv.w, 0 };
v2 b_uv = uv.position + v2{0 , 0 };
v2 c_uv = uv.position + v2{0 , uv.h};
v2 d_uv = uv.position + v2{uv.w, uv.h};
r_2d_immediate_quad(a, b, c, d, color, a_uv, b_uv, c_uv, d_uv, texture);
}
void r_2d_immediate_rounded_rectangle(Rect r, f32 radius, v4 color)
{
radius = clamp(0, minimum(abs(r.w), abs(r.h)) / 2, radius);
// Should compute PIXELS_PER_SEGMENT from the size of the radius
u32 num_of_segments = floor(0.5 + radius * TAU * 0.25 / PIXELS_PER_SEGMENT);
// Split into 9 sections:
// - 5 quads (center, top, bottom, left, right)
// - 4 semicircles (corners)
// Inner vertices (CCW starting from 1st quadrant/upper-right)
v2 i0, i1, i2, i3;
i0.x = r.x + r.w - radius;
i0.y = r.y + radius;
i1.x = r.x + radius;
i1.y = r.y + radius;
i2.x = r.x + radius;
i2.y = r.y + r.h - radius;
i3.x = r.x + r.w - radius;
i3.y = r.y + r.h - radius;
// Outer vertices
v2 o0, o1, o2, o3, o4, o5, o6, o7;
o0.x = i0.x;
o0.y = i0.y - radius;
o1.x = i1.x;
o1.y = i1.y - radius;
o2.x = i1.x - radius;
o2.y = i1.y;
o3.x = i2.x - radius;
o3.y = i2.y;
o4.x = i2.x;
o4.y = i2.y + radius;
o5.x = i3.x;
o5.y = i3.y + radius;
o6.x = i3.x + radius;
o6.y = i3.y;
o7.x = i0.x + radius;
o7.y = i0.y;
// Reserve space for vertices
u32 vertices_count = 30; // 5 quads, specified by 2 triangles each = 5 quads * 2 triangles * 3 vertices = 30 vertices
vertices_count += num_of_segments * 12; // Add corner semicircles = 4 corners * N triangles * 3 vertices = N * 12
// Build 5 quads
v2 vertices[vertices_count] = {
i0, i1, i2, i0, i2, i3, // Center quad: i0, i1, i2, i3
o0, o1, i1, o0, i1, i0, // Top quad: o0, o1, i1, i0
i1, o2, o3, i1, o3, i2, // Left quad: i1, o2, o3, i2
i3, i2, o4, i3, o4, o5, // Bottom quad: i3, i2, o4, o5
o7, i0, i3, o7, i3, o6 // Right quad: o7, i0, i3, o6
};
u32 corner_offset = 30;
// Corner semicircles
f32 factor = TAU * .25 / num_of_segments;
v2 inner_vertices[4] = {i0, i1, i2, i3};
for(u32 quadrant = 0; quadrant < 4; quadrant++)
{
for(u32 i = quadrant*num_of_segments; i < (quadrant+1)*num_of_segments; i++)
{
v2 inner = inner_vertices[quadrant];
v2 a = inner + radius * v2{cos( i * factor), -sin( i * factor)};
v2 b = inner + radius * v2{cos((i+1) * factor), -sin((i+1) * factor)};
vertices[corner_offset + 3*i + 0] = inner;
vertices[corner_offset + 3*i + 1] = a;
vertices[corner_offset + 3*i + 2] = b;
}
}
r_2d_immediate_mesh(vertices_count, vertices, color, NULL, NULL);
}
void r_2d_immediate_mesh(u64 count, v2 *vertices, v4 color, v2 *uvs, r_texture *texture)
{
glDisable(GL_DEPTH_TEST);
// Shader
glUseProgram(r_render_state.shader_2d.id);
// Texture
set_texture_in_shader(&r_render_state.shader_2d, texture);
// Vertex buffer data
GLuint gl_VAO, gl_vertices, gl_uvs;
glGenVertexArrays(1, &gl_VAO);
glBindVertexArray(gl_VAO);
glGenBuffers(1, &gl_vertices);
glGenBuffers(1, &gl_uvs);
glBindBuffer(GL_ARRAY_BUFFER, gl_vertices);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(v2), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
//glDisableVertexAttribArray(1);
glVertexAttrib4f(1, color.r, color.g, color.b, color.a);
glBindBuffer(GL_ARRAY_BUFFER, gl_uvs);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(v2), uvs , GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
// Draw
glDrawArrays(GL_TRIANGLES, 0, count);
// Deinitvoid r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture);
glDeleteBuffers(1, &gl_vertices);
glDeleteBuffers(1, &gl_uvs);
glDeleteVertexArrays(1, &gl_VAO);
glEnable(GL_DEPTH_TEST);
}
void r_2d_immediate_rectangle_outline(Rect r, v4 color, f32 thickness)
{
v2 vertices[5];
vertices[0] = r.position + v2{ 0, 0};
vertices[1] = r.position + v2{ 0, r.h};
vertices[2] = r.position + v2{r.w, r.h};
vertices[3] = r.position + v2{r.w, 0};
vertices[4] = r.position + v2{ 0, 0};
r_2d_immediate_polygonal_chain(5, vertices, color, thickness);
}
void r_2d_immediate_rounded_rectangle_outline(Rect r, f32 radius, v4 color, f32 thickness)
{
radius = clamp(0, minimum(abs(r.w), abs(r.h)) / 2, radius);
// Should compute PIXELS_PER_SEGMENT from the size of the radius
u32 num_of_segments = floor(0.5 + radius * TAU * 0.25 / PIXELS_PER_SEGMENT);
// Split into 9 sections:
// - 5 quads (center, top, bottom, left, right)
// - 4 semicircles (corners)
// Inner vertices (CCW starting from 1st quadrant/upper-right)
v2 i0, i1, i2, i3;
i0.x = r.x + r.w - radius;
i0.y = r.y + radius;
i1.x = r.x + radius;
i1.y = r.y + radius;
i2.x = r.x + radius;
i2.y = r.y + r.h - radius;
i3.x = r.x + r.w - radius;
i3.y = r.y + r.h - radius;
// Reserve space for vertices
u32 vertices_count = 1 + 4 + 4*num_of_segments; // Starting vertex (1) + one for each side (4) + one for each segment (4*num_of_segments)
v2 inner_vertices[4] = {i0, i1, i2, i3};
v2 vertices[vertices_count] = {
i3 + v2{radius, 0} // Starting vertex
};
u32 v_index = 1;
// Corner semicircles
f32 factor = TAU * .25 / num_of_segments;
for(u32 quadrant = 0; quadrant < 4; quadrant++)
{
v2 inner = inner_vertices[quadrant];
for(u32 i = quadrant*num_of_segments; i < (quadrant+1)*num_of_segments; i++)
{
v2 a = inner + radius * v2{cos( i * factor), -sin( i * factor)};
vertices[v_index] = a;
v_index++;
}
vertices[v_index] = inner + radius * v2{cos( (quadrant+1)*num_of_segments * factor), -sin( (quadrant+1)*num_of_segments * factor)};
v_index++;
}
r_2d_immediate_polygonal_chain(vertices_count, vertices, color, thickness);
}
void r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture)
{
glDisable(GL_DEPTH_TEST);
// Shader
glUseProgram(r_render_state.shader_2d.id);
// Texture
set_texture_in_shader(&r_render_state.shader_2d, texture);
// Draw
glBindVertexArray(mesh->gl_VAO);
glDrawArrays(GL_TRIANGLES, 0, mesh->count);
glEnable(GL_DEPTH_TEST);
}
// Internal use functions
static void set_texture_in_shader(r_shader *shader, r_texture *texture, v4 color, u32 texture_index)
{
// Remember to call glUseProgram before using this
if(texture)
{
glUniform1i(shader->has_texture[texture_index], 1);
glUniform1i(shader->texture[texture_index], 0);
glUniform1i(shader->texture_channels[texture_index], r_texture_channels(texture));
glActiveTexture(GL_TEXTURE0 + texture_index);
glBindTexture(GL_TEXTURE_2D, texture->gl_id);
glUniform4f(shader->color[texture_index], color.r, color.g, color.b, color.a);
}
else
{
glUniform1i(shader->has_texture[texture_index], 0);
}
}

27
code/render/2d.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef _PIUMA_RENDER_2D_H_
#define _PIUMA_RENDER_2D_H_
#include "../lib/types.h"
#include "../lib/math.h"
#include "../lib/geometry.h"
#include "primitives.h"
// Immediate functions: less efficient but easy to use
void r_2d_immediate_segment(v2 a, v2 b, v4 a_color, v4 b_color, f32 thickness = 1.0f);
void r_2d_immediate_polygonal_chain(u64 count, v2 *vertices, v4 color, f32 thickness = 1.0f);
void r_2d_immediate_triangle(v2 a, v2 b, v2 c, v4 a_color, v4 b_color, v4 c_color, v2 a_uv = {0,0}, v2 b_uv = {0,0}, v2 c_uv = {0,0}, r_texture *texture = NULL);
void r_2d_immediate_quad(v2 a, v2 b, v2 c, v2 d, v4 color, v2 a_uv = {0,0}, v2 b_uv = {0,0}, v2 c_uv = {0,0}, v2 d_uv = {0,0}, r_texture *texture = NULL);
void r_2d_immediate_rectangle(Rect r, v4 color, Rect uv = {0,0,1,1}, r_texture *texture = NULL);
void r_2d_immediate_rounded_rectangle(Rect r, f32 radius, v4 color);
void r_2d_immediate_mesh(u64 count, v2 *vertices, v4 color, v2 *uvs = NULL, r_texture *texture = NULL);
void r_2d_immediate_rectangle_outline(Rect r, v4 color, f32 thickness = 1.0f);
void r_2d_immediate_rounded_rectangle_outline(Rect r, f32 radius, v4 color, f32 thickness = 1.0f);
// Draw functions: usual interface (create objects / send to gpu, draw call, remove from gpu)
void r_2d_draw_mesh(r_2d_mesh *mesh, r_texture *texture);
// @Feature: @Performance: something for text rendering (a lot of quads/rects)
// Maybe we could send to the gpu the following: texture of the characters, array of uvs for indexing the texture, character position + index of its uv.
#endif

52
code/render/gl_helpers.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef _PIUMA_RENDER_GL_HELPERS_H_
#define _PIUMA_RENDER_GL_HELPERS_H_
#include "../debug/logger.h"
inline void APIENTRY glDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char *message, const void *userParam)
{
// ignore non-significant error/warning codes
if(id == 131169 || id == 131185 || id == 131218 || id == 131204)
return;
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
return;
// @Cleanup: LOG does not replace printf here. We need to merge the multiple printfs into a single log message
LOG(LOG_DEBUG, "---------------");
LOG(LOG_DEBUG, "Debug message (%u): %s", id, message);
switch (source)
{
case GL_DEBUG_SOURCE_API: LOG(LOG_DEBUG, "Source: API"); break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM: LOG(LOG_DEBUG, "Source: Window System"); break;
case GL_DEBUG_SOURCE_SHADER_COMPILER: LOG(LOG_DEBUG, "Source: Shader Compiler"); break;
case GL_DEBUG_SOURCE_THIRD_PARTY: LOG(LOG_DEBUG, "Source: Third Party"); break;
case GL_DEBUG_SOURCE_APPLICATION: LOG(LOG_DEBUG, "Source: Application"); break;
case GL_DEBUG_SOURCE_OTHER: LOG(LOG_DEBUG, "Source: Other"); break;
}
switch (type)
{
case GL_DEBUG_TYPE_ERROR: LOG(LOG_DEBUG, "Type: Error"); break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: LOG(LOG_DEBUG, "Type: Deprecated Behaviour"); break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: LOG(LOG_DEBUG, "Type: Undefined Behaviour"); break;
case GL_DEBUG_TYPE_PORTABILITY: LOG(LOG_DEBUG, "Type: Portability"); break;
case GL_DEBUG_TYPE_PERFORMANCE: LOG(LOG_DEBUG, "Type: Performance"); break;
case GL_DEBUG_TYPE_MARKER: LOG(LOG_DEBUG, "Type: Marker"); break;
case GL_DEBUG_TYPE_PUSH_GROUP: LOG(LOG_DEBUG, "Type: Push Group"); break;
case GL_DEBUG_TYPE_POP_GROUP: LOG(LOG_DEBUG, "Type: Pop Group"); break;
case GL_DEBUG_TYPE_OTHER: LOG(LOG_DEBUG, "Type: Other"); break;
}
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH: LOG(LOG_DEBUG, "Severity: high"); break;
case GL_DEBUG_SEVERITY_MEDIUM: LOG(LOG_DEBUG, "Severity: medium"); break;
case GL_DEBUG_SEVERITY_LOW: LOG(LOG_DEBUG, "Severity: low"); break;
case GL_DEBUG_SEVERITY_NOTIFICATION: LOG(LOG_DEBUG, "Severity: notification"); break;
}
LOG(LOG_DEBUG, "");
}
#endif

0
code/render/lights.cpp Normal file
View File

44
code/render/lights.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef _PIUMA_RENDER_LIGHTS_H_
#define _PIUMA_RENDER_LIGHTS_H_
struct r_sun_light
{
v3 direction;
f32 _padding0;
v3 color;
f32 intensity;
};
struct r_point_light
{
v3 position;
f32 _padding0;
v3 color;
f32 intensity;
};
struct r_spot_light
{
v3 position;
f32 inner_radius;
v3 color;
f32 intensity;
v3 direction;
f32 outer_radius;
};
#define MAX_SUN_LIGHTS 4
#define MAX_POINT_LIGHTS 128
#define MAX_SPOT_LIGHTS 128
struct r_light_container
{
u32 sun_light_count;
u32 point_light_count;
u32 spot_light_count;
f32 ambient_light;
r_sun_light sun_lights[MAX_SUN_LIGHTS];
r_point_light point_lights[MAX_POINT_LIGHTS];
r_spot_light spot_lights[MAX_SPOT_LIGHTS];
};
#endif

79
code/render/object.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "object.h"
#include "../lib/math.h"
#include "../lib/geometry.h"
#include "../platform.h"
#include "string.h"
#include "state.h"
#include "2d.h"
r_object r_object_create(u64 count, r_mesh **meshes, r_material **materials, v3 position, v3 rotation, v3 scale)
{
r_object object;
object.meshes = (r_mesh**)p_alloc(count * sizeof(r_mesh*));
object.mesh_material = (r_material**)p_alloc(count * sizeof(r_material*));
object.mesh_local_transform = (m4*)p_alloc(count * sizeof(m4));
memcpy(object.meshes, meshes, count * sizeof(r_mesh*));
memcpy(object.mesh_material, materials, count * sizeof(r_material*));
for(u64 i = 0; i < count; i++)
object.mesh_local_transform[i] = m4_identity;
object.count = count;
object.position = position;
object.rotation = rotation;
object.scale = scale;
object.has_shadow = true;
return object;
}
r_object r_object_allocate(u64 count)
{
r_object object;
object.meshes = (r_mesh**)p_alloc(count * sizeof(r_mesh*));
object.mesh_material = (r_material**)p_alloc(count * sizeof(r_material*));
object.mesh_local_transform = (m4*)p_alloc(count * sizeof(m4));
for(u64 i = 0; i < count; i++)
object.mesh_local_transform[i] = m4_identity;
object.count = count;
object.position = v3{0,0,0};
object.rotation = v3{0,0,0};
object.scale = v3{1,1,1};
object.has_shadow = true;
return object;
}
void r_object_destroy(r_object *object)
{
p_free(object->mesh_material);
p_free(object->meshes);
p_free(object->mesh_local_transform);
object->mesh_material = NULL;
object->meshes = NULL;
object->mesh_local_transform = NULL;
object->count = 0;
}
m4 r_object_transform_matrix(r_object *object)
{
// @Performance: replace with rototranslation matrix
return translation_v3(object->position) * rotation_v3(object->rotation) * scale_v3(object->scale);
}
m4 r_object_mesh_transform_matrix(r_object *object, u64 mesh_i)
{
// @Performance: replace with rototranslation matrix
if(object->mesh_local_transform)
return r_object_transform_matrix(object) * object->mesh_local_transform[mesh_i];
return r_object_transform_matrix(object);
}

64
code/render/object.h Normal file
View File

@@ -0,0 +1,64 @@
#ifndef _PIUMA_RENDER_OBJECT_H_
#define _PIUMA_RENDER_OBJECT_H_
#include "primitives.h"
#include "shader.h"
struct r_material
{
// @Feature: PBR materials
r_shader *shader;
r_texture *albedo_texture;
v4 albedo_factor;
r_texture *metallic_texture;
f32 metallic_factor;
r_texture *roughness_texture;
f32 roughness_factor;
r_texture *normal_texture;
r_texture *emissive_texture;
v4 emissive_factor;
/*
albedo |
metallic | --> Disney + UE4 paper
roughness / specular |
cavity |
subsurface |
anisotropy | --> Disney paper
clearcoat |
sheen |
ambient occlusion | --> Valve Source2, same as cavity?
emission
normals
*/
};
struct r_object
{
// @Feature: actually support multiple meshes/materials in the implementation code
r_mesh **meshes;
r_material **mesh_material;
m4 *mesh_local_transform;
u64 count;
v3 scale;
v3 position;
v3 rotation;
bool has_shadow;
};
r_object r_object_create(u64 count, r_mesh **meshes, r_material **materials, v3 position = {0,0,0}, v3 rotation = {0,0,0}, v3 scale = {1,1,1});
r_object r_object_allocate(u64 count);
void r_object_destroy(r_object *object);
m4 r_object_transform_matrix(r_object *object);
m4 r_object_mesh_transform_matrix(r_object *object, u64 mesh_i);
#endif

424
code/render/primitives.cpp Normal file
View File

@@ -0,0 +1,424 @@
#include "primitives.h"
#include "../platform.h"
#include <string.h>
// GL utils
static GLint gl_texture_internal_format(u32 texture_flags);
static GLenum gl_texture_format (u32 texture_flags);
static GLenum gl_texture_type (u32 texture_flags);
// Texture
r_texture r_texture_create(u8 *data, v2s size, u32 flags)
{
r_texture texture;
texture.data = data;
texture.size = size;
texture.flags = flags | R_TEXTURE_INITIALIZED;
glGenTextures(1, &texture.gl_id);
glBindTexture(GL_TEXTURE_2D, texture.gl_id);
GLint internal_format = gl_texture_internal_format(flags);
GLenum format = gl_texture_format (flags);
GLenum type = gl_texture_type (flags);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, format, type, data);
if(flags & R_TEXTURE_NO_MIPMAP)
{
// The default for GL_TEXTURE_{MIN,MAG}_FILTER is GL_NEAREST_MIPMAP_LINEAR, but we are not using mipmaps for this texture. If we don't change this parameter, the image will not render.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
else
{
glGenerateMipmap(GL_TEXTURE_2D);
}
if(flags & R_TEXTURE_DONT_OWN)
texture.data = NULL;
return texture;
}
void r_texture_resize(r_texture *texture, v2s size)
{
glBindTexture(GL_TEXTURE_2D, texture->gl_id);
GLint internal_format = gl_texture_internal_format(texture->flags);
GLenum format = gl_texture_format (texture->flags);
GLenum type = gl_texture_type (texture->flags);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, size.x, size.y, 0, format, type, NULL);
texture->size = size;
}
void r_texture_update(r_texture *texture, u8 *data, v2s size, v2s position, u32 stride)
{
glBindTexture(GL_TEXTURE_2D, texture->gl_id);
GLenum format = gl_texture_format(texture->flags);
GLenum type = gl_texture_type (texture->flags);
glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, size.x, size.y, format, type, data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if(texture->flags & R_TEXTURE_NO_MIPMAP)
{
// The default for GL_TEXTURE_{MIN,MAG}_FILTER is GL_NEAREST_MIPMAP_LINEAR, but we are not using mipmaps for this texture. If we don't change this parameter, the image will not render.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
else
{
glGenerateMipmap(GL_TEXTURE_2D);
}
}
void r_texture_destroy(r_texture *texture)
{
glDeleteTextures(1, &texture->gl_id);
if(texture->data && !(texture->flags & R_TEXTURE_DONT_OWN))
p_free(texture->data);
texture->size = {0,0};
texture->flags = R_TEXTURE_DESTROYED;
}
s32 r_texture_channels(r_texture *texture)
{
if(texture->flags & R_TEXTURE_ALPHA)
return 1;
if(texture->flags & R_TEXTURE_RGB)
return 3;
if(texture->flags & (R_TEXTURE_RGBA | R_TEXTURE_SRGB))
return 4;
return 4;
}
// Cubemap
r_cubemap r_cubemap_create(float *data[6], v2s size, u32 flags)
{
r_cubemap cubemap;
for(u32 i = 0; i < 6; i++)
cubemap.data[i] = data[i];
cubemap.size = size;
cubemap.flags = flags | R_CUBEMAP_INITIALIZED;
glGenTextures(1, &cubemap.gl_id);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.gl_id);
for(u32 i = 0; i < 6; i++)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size.x, size.y, 0, GL_RGB, GL_FLOAT, data[i]);
if(flags & R_CUBEMAP_DONT_OWN)
cubemap.data[i] = NULL;
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
return cubemap;
}
void r_cubemap_destroy(r_cubemap *cubemap)
{
glDeleteTextures(1, &cubemap->gl_id);
for(u32 i = 0; i < 6; i++)
{
if(cubemap->data[i] && !(cubemap->flags & R_CUBEMAP_DONT_OWN))
p_free(cubemap->data[i]);
}
cubemap->size = {0,0};
cubemap->flags = R_CUBEMAP_DESTROYED;
}
// 2D mesh
r_2d_mesh r_2d_mesh_create(u64 count, v2 *vertices, v4 *colors, v2 *uvs)
{
r_2d_mesh mesh;
mesh.vertices = vertices;
mesh.colors = colors;
mesh.uvs = uvs;
mesh.count = count;
glGenVertexArrays(1, &mesh.gl_VAO);
glBindVertexArray(mesh.gl_VAO);
glGenBuffers(1, &mesh.gl_vertex_buffer);
glGenBuffers(1, &mesh.gl_color_buffer);
glGenBuffers(1, &mesh.gl_uv_buffer);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v2), mesh.vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_color_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v4), mesh.colors, GL_STATIC_DRAW);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(v4), (void*)0);
if(mesh.uvs)
{
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_uv_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.count * sizeof(v2), mesh.uvs, GL_STATIC_DRAW);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
}
return mesh;
}
void r_2d_mesh_destroy(r_2d_mesh *mesh)
{
glBindVertexArray(mesh->gl_VAO);
glDeleteBuffers(1, &mesh->gl_vertex_buffer);
glDeleteBuffers(1, &mesh->gl_color_buffer);
glDeleteBuffers(1, &mesh->gl_uv_buffer);
glDeleteVertexArrays(1, &mesh->gl_VAO);
if(mesh->vertices)
p_free(mesh->vertices);
if(mesh->colors)
p_free(mesh->colors);
if(mesh->uvs)
p_free(mesh->uvs);
}
// 3D mesh
r_mesh r_mesh_create(u64 indices_count, u32 *indices, u64 vertices_count, v3 *vertices, v3 *normals, v3 *tangents, v2 *uvs)
{
r_mesh mesh;
mesh.vertices = vertices;
mesh.normals = normals;
mesh.tangents = tangents;
mesh.uvs = uvs;
mesh.vertices_count = vertices_count;
mesh.indices = indices;
mesh.indices_count = indices_count;
glGenVertexArrays(1, &mesh.gl_VAO);
glBindVertexArray(mesh.gl_VAO);
glGenBuffers(1, &mesh.gl_vertex_buffer);
glGenBuffers(1, &mesh.gl_normal_buffer);
glGenBuffers(1, &mesh.gl_tangent_buffer);
glGenBuffers(1, &mesh.gl_uv_buffer);
glGenBuffers(1, &mesh.gl_index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.gl_index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices_count * sizeof(u32), mesh.indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
if(mesh.normals)
{
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_normal_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.normals, GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
}
if(mesh.tangents)
{
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_tangent_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v3), mesh.tangents, GL_STATIC_DRAW);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(v3), (void*)0);
}
if(mesh.uvs)
{
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, mesh.gl_uv_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.vertices_count * sizeof(v2), mesh.uvs, GL_STATIC_DRAW);
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(v2), (void*)0);
}
return mesh;
}
void r_mesh_destroy(r_mesh *mesh)
{
glBindVertexArray(mesh->gl_VAO);
glDeleteBuffers(1, &mesh->gl_vertex_buffer);
glDeleteBuffers(1, &mesh->gl_normal_buffer);
glDeleteBuffers(1, &mesh->gl_uv_buffer);
glDeleteBuffers(1, &mesh->gl_index_buffer);
glDeleteVertexArrays(1, &mesh->gl_VAO);
if(mesh->vertices)
p_free(mesh->vertices);
if(mesh->normals)
p_free(mesh->normals);
if(mesh->uvs)
p_free(mesh->uvs);
if(mesh->indices)
p_free(mesh->indices);
}
// Common meshes
r_mesh r_mesh_build_cube()
{
v3 v[24] = {
{0.5, 0.5, -0.5},
{0.5, 0.5, -0.5},
{0.5, 0.5, -0.5},
{0.5, -0.5, -0.5},
{0.5, -0.5, -0.5},
{0.5, -0.5, -0.5},
{0.5, 0.5, 0.5},
{0.5, 0.5, 0.5},
{0.5, 0.5, 0.5},
{0.5, -0.5, 0.5},
{0.5, -0.5, 0.5},
{0.5, -0.5, 0.5},
{-0.5, 0.5, -0.5},
{-0.5, 0.5, -0.5},
{-0.5, 0.5, -0.5},
{-0.5, -0.5, -0.5},
{-0.5, -0.5, -0.5},
{-0.5, -0.5, -0.5},
{-0.5, 0.5, 0.5},
{-0.5, 0.5, 0.5},
{-0.5, 0.5, 0.5},
{-0.5, -0.5, 0.5},
{-0.5, -0.5, 0.5},
{-0.5, -0.5, 0.5}
};
v3 n[24] = {
{0.0, 1.0, 0.0},
{1.0, 0.0, 0.0},
{0.0, 0.0, -1.0},
{0.0, -1.0, 0.0},
{1.0, 0.0, 0.0},
{0.0, 0.0, -1.0},
{0.0, 1.0, 0.0},
{0.0, 0.0, 1.0},
{1.0, 0.0, 0.0},
{0.0, 0.0, 1.0},
{0.0, -1.0, 0.0},
{1.0, 0.0, 0.0},
{0.0, 1.0, 0.0},
{-1.0, 0.0, 0.0},
{0.0, 0.0, -1.0},
{-1.0, 0.0, 0.0},
{0.0, -1.0, 0.0},
{0.0, 0.0, -1.0},
{0.0, 1.0, 0.0},
{0.0, 0.0, 1.0},
{-1.0, 0.0, 0.0},
{0.0, 0.0, 1.0},
{-1.0, 0.0, 0.0},
{0.0, -1.0, 0.0}
};
u32 i[36] = {12, 6, 0, 7, 21, 9, 20, 15, 22, 3, 23, 16, 1, 11, 4, 14, 5, 17, 12, 18, 6, 7, 19, 21, 20, 13, 15, 3, 10, 23, 1, 8, 11, 14, 2, 5};
v3 *vertices = (v3*)p_alloc(24 * sizeof(v3));
v3 *normals = (v3*)p_alloc(24 * sizeof(v3));
u32 *indices = (u32*)p_alloc(36 * sizeof(u32));
memcpy(vertices, v, 24 * sizeof(v3));
memcpy(normals , n, 24 * sizeof(v3));
memcpy(indices , i, 36 * sizeof(u32));
return r_mesh_create(36, indices, 24, vertices, normals, NULL, NULL);
}
// Framebuffers
r_framebuffer r_framebuffer_create(v2s size, u32 flags)
{
r_framebuffer fb;
fb.flags = flags;
fb.size = size;
glGenFramebuffers(1, &fb.gl_id);
glBindFramebuffer(GL_FRAMEBUFFER, fb.gl_id);
fb.color_texture = r_texture_create(NULL, size, R_TEXTURE_RGBA | R_TEXTURE_HDR | R_TEXTURE_NO_MIPMAP);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.color_texture.gl_id, 0);
fb.gl_depth_id = 0;
if(flags & R_FRAMEBUFFER_DEPTH)
{
glGenRenderbuffers(1, &fb.gl_depth_id);
glBindRenderbuffer(GL_RENDERBUFFER, fb.gl_depth_id);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.gl_depth_id);
}
return fb;
}
void r_framebuffer_destroy(r_framebuffer *fb)
{
r_texture_destroy(&fb->color_texture);
if(fb->flags & R_FRAMEBUFFER_DEPTH)
glDeleteRenderbuffers(1, &fb->gl_depth_id);
glDeleteFramebuffers(1, &fb->gl_id);
}
void r_framebuffer_update_size(r_framebuffer *fb, v2s size)
{
glBindFramebuffer(GL_FRAMEBUFFER, fb->gl_id);
r_texture_resize(&fb->color_texture, size);
if(fb->flags & R_FRAMEBUFFER_DEPTH)
{
glBindRenderbuffer(GL_RENDERBUFFER, fb->gl_depth_id);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y);
}
fb->size = size;
}
// GL utils
static GLint gl_texture_internal_format(u32 texture_flags)
{
if(texture_flags & R_TEXTURE_ALPHA)
return GL_RED;
else if(texture_flags & R_TEXTURE_RGB)
return (texture_flags & R_TEXTURE_HDR) ? GL_RGB16F : GL_RGB;
else if(texture_flags & R_TEXTURE_RGBA)
return (texture_flags & R_TEXTURE_HDR) ? GL_RGBA16F : GL_RGBA;
else if(texture_flags & R_TEXTURE_SRGB)
return GL_SRGB_ALPHA;
return GL_RGBA;
}
static GLenum gl_texture_format(u32 texture_flags)
{
if(texture_flags & R_TEXTURE_ALPHA)
return GL_RED;
else if(texture_flags & R_TEXTURE_RGB)
return GL_RGB;
else if(texture_flags & R_TEXTURE_RGBA)
return GL_RGBA;
else if(texture_flags & R_TEXTURE_SRGB)
return GL_RGBA;
return GL_RGBA;
}
static GLenum gl_texture_type(u32 texture_flags)
{
if(texture_flags & R_TEXTURE_HDR)
return GL_FLOAT;
return GL_UNSIGNED_BYTE;
}

149
code/render/primitives.h Normal file
View File

@@ -0,0 +1,149 @@
#ifndef _PIUMA_RENDER_PRIMITIVES_H_
#define _PIUMA_RENDER_PRIMITIVES_H_
#include "../lib/types.h"
#include "../lib/math.h"
#include "GL/glcorearb.h"
enum r_texture_flags : u32
{
R_TEXTURE_NONE = 0x00,
R_TEXTURE_ALPHA = 0x01,
R_TEXTURE_RGB = 0x02,
R_TEXTURE_RGBA = 0x04,
R_TEXTURE_SRGB = 0x08,
R_TEXTURE_HDR = 0x10,
R_TEXTURE_NO_MIPMAP = 0x00010000,
R_TEXTURE_DONT_OWN = 0x20000000,
R_TEXTURE_INITIALIZED = 0x40000000,
R_TEXTURE_DESTROYED = 0x80000000
};
struct r_texture
{
u8 *data;
v2s size;
u32 flags;
// OpenGL
GLuint gl_id;
};
r_texture r_texture_create(u8 *data, v2s size, u32 flags);
void r_texture_destroy(r_texture *texture);
void r_texture_resize(r_texture *texture, v2s size);
void r_texture_update(r_texture *texture, u8 *data, v2s size, v2s position = {0,0}, u32 stride = 0);
s32 r_texture_channels(r_texture *texture);
enum r_cubemap_flags : u32
{
R_CUBEMAP_NONE = 0x00,
// R_TEXTURE_ALPHA = 0x01,
// R_TEXTURE_RGB = 0x02,
// R_TEXTURE_RGBA = 0x04,
// R_TEXTURE_SRGB = 0x08,
R_CUBEMAP_DONT_OWN = 0x20000000,
R_CUBEMAP_INITIALIZED = 0x40000000,
R_CUBEMAP_DESTROYED = 0x80000000
};
struct r_cubemap
{
float *data[6];
v2s size;
u32 flags;
// OpenGL
GLuint gl_id;
};
r_cubemap r_cubemap_create(float *data[6], v2s size, u32 flags);
void r_cubemap_destroy(r_cubemap *cubemap);
struct r_2d_mesh
{
v2 *vertices;
v4 *colors;
v2 *uvs;
u64 count;
// OpenGL
GLuint gl_VAO;
GLuint gl_vertex_buffer;
GLuint gl_color_buffer;
GLuint gl_uv_buffer;
};
r_2d_mesh r_2d_mesh_create(u64 count, v2 *vertices, v4 *colors, v2 *uvs = NULL);
void r_2d_mesh_destroy(r_2d_mesh *mesh);
// r_2d_mesh_update/change
struct r_mesh
{
v3 *vertices;
v3 *normals;
v3 *tangents;
v2 *uvs;
u64 vertices_count;
u32 *indices;
u64 indices_count;
// OpenGL
GLuint gl_VAO;
GLuint gl_vertex_buffer;
GLuint gl_normal_buffer;
GLuint gl_tangent_buffer;
GLuint gl_uv_buffer;
GLuint gl_index_buffer;
};
r_mesh r_mesh_create(u64 indices_count, u32 *indices, u64 vertices_count, v3 *vertices, v3 *normals = NULL, v3 *tangents = NULL, v2 *uvs = NULL);
void r_mesh_destroy(r_mesh *mesh);
// r_mesh_update/change
// Common meshes
r_mesh r_mesh_build_cube();
// Framebuffers
enum r_framebuffer_flags : u32
{
R_FRAMEBUFFER_NONE = 0,
R_FRAMEBUFFER_DEPTH,
};
struct r_framebuffer
{
u32 gl_id;
u32 gl_depth_id;
r_texture color_texture;
v2s size;
u32 flags;
};
r_framebuffer r_framebuffer_create(v2s size, u32 flags);
void r_framebuffer_destroy(r_framebuffer *fb);
void r_framebuffer_update_size(r_framebuffer *fb, v2s size);
#endif

513
code/render/render.cpp Normal file
View File

@@ -0,0 +1,513 @@
#include "render.h"
#include "state.h"
#include "../platform.h"
#include "../lib/math.h"
#include "../debug/logger.h"
#include "gl_helpers.h"
#include "string.h"
#include "../camera.h" // @Cleanup: remove dependency from camera
r_state r_render_state;
void r_init()
{
// OpenGL debug messages
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(glDebugOutput, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
// Depth test
glEnable(GL_DEPTH_TEST);
// Face culling
glEnable(GL_CULL_FACE);
// Texture pixel alignment (default is 4, so rgb textures should have every pixel padded to 4 bytes. With this we can have 3 byte pixels with no padding between eachother)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Blending
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// Cubemap
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
// Load shaders
if(!r_shader_from_files(&r_render_state.shader_2d, "shaders/2d.vert", "shaders/2d.frag"))
{
LOG(LOG_ERROR, "Cannot load 2D shader.");
}
if(!r_shader_from_files(&r_render_state.shader_postprocessing, "shaders/postprocessing.vert", "shaders/postprocessing.frag"))
{
LOG(LOG_ERROR, "Cannot load postprocessing shader.");
}
if(!r_shader_from_files(&r_render_state.shader_pbr, "shaders/pbr.vert", "shaders/pbr.frag"))
{
LOG(LOG_ERROR, "Cannot load pbr shader.");
}
if(!r_shader_from_files(&r_render_state.shader_shadow_map, "shaders/shadow_map.vert", "shaders/shadow_map.frag"))
{
LOG(LOG_ERROR, "Cannot load shadow_map shader.");
}
if(!r_shader_from_files(&r_render_state.shader_environment_map, "shaders/environment_map.vert", "shaders/environment_map.frag"))
{
LOG(LOG_ERROR, "Cannot load environment_map shader.");
}
// Screen size
u32 width, height;
p_window_dimensions(&width, &height);
glViewport(0, 0, width, height);
/* We have to apply some post-processing effects only to the 3D world
* and some effects to both the 3D world and the HUD.
*
* So we make 2 framebuffers:
* 1. target for 3D world
* 2. target for HUD
* After rendering, we apply the correct post-processing effects and
* merge them toghether.
*/
// Init framebuffers
v2s size = {width, height};
r_render_state.framebuffer_SCREEN = {.gl_id = 0, .size = size, .flags = 0}; // This is a special framebuffer, because it's already there. We don't need to create it
r_render_state.framebuffer_HUD = r_framebuffer_create(size, 0);
r_render_state.framebuffer_3D = r_framebuffer_create(size, R_FRAMEBUFFER_DEPTH);
// Default framebuffer
r_framebuffer_select(&r_render_state.framebuffer_SCREEN);
// Init fullscreen quad
glGenVertexArrays(1, &r_render_state.gl_screen_quad_VAO);
glBindVertexArray(r_render_state.gl_screen_quad_VAO);
glGenBuffers(1, &r_render_state.gl_screen_quad_VBO);
glBindBuffer(GL_ARRAY_BUFFER, r_render_state.gl_screen_quad_VBO);
v2 quad_vertices[2*6] =
{
// position UV coords
v2{-1, 1}, v2{0, 1},
v2{-1, -1}, v2{0, 0},
v2{ 1, -1}, v2{1, 0},
v2{-1, 1}, v2{0, 1},
v2{ 1, -1}, v2{1, 0},
v2{ 1, 1}, v2{1, 1}
};
glBufferData(GL_ARRAY_BUFFER, 2*6 * sizeof(v2), quad_vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(v2), (void*)(0));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2*sizeof(v2), (void*)(sizeof(v2)));
// Render state
r_size_update(width, height);
}
void r_deinit()
{
r_framebuffer_destroy(&r_render_state.framebuffer_HUD);
r_framebuffer_destroy(&r_render_state.framebuffer_3D);
glDeleteBuffers(1, &r_render_state.gl_screen_quad_VBO);
glDeleteVertexArrays(1, &r_render_state.gl_screen_quad_VAO);
}
void r_size_update(u32 width, u32 height)
{
// Screen size
r_render_state.width = width;
r_render_state.height = height;
// Update shaders
glUseProgram(r_render_state.shader_2d.id);
glUniform1i(r_render_state.shader_2d.width , width);
glUniform1i(r_render_state.shader_2d.height, height);
// Update framebuffers size
v2s size = {width, height};
r_render_state.framebuffer_SCREEN.size = size;
r_framebuffer_update_size(&r_render_state.framebuffer_HUD, size);
r_framebuffer_update_size(&r_render_state.framebuffer_3D , size);
}
void r_time_update(f64 time)
{
r_render_state.time = time;
}
void r_clear(v4 color)
{
glClearColor(color.r, color.g, color.b, color.a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void r_swap()
{
p_graphics_swap();
}
r_scene r_scene_create()
{
r_scene scene;
scene.view_matrix = m4_identity;
scene.view_matrix_inverse = m4_identity;
scene.projection_matrix = m4_identity;
scene.projection_matrix_inverse = m4_identity;
scene.objects = NULL;
scene.objects_count = 0;
// Init lights memory
memset(&scene.lights, 0, sizeof(r_light_container));
glGenBuffers(1, &scene.gl_lights);
glBindBuffer(GL_UNIFORM_BUFFER, scene.gl_lights);
glBufferData(GL_UNIFORM_BUFFER, sizeof(r_light_container), &scene.lights, GL_STATIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, scene.gl_lights);
return scene;
}
void r_scene_destroy(r_scene *scene)
{
glDeleteBuffers(1, &scene->gl_lights);
r_cubemap_destroy(&scene->environment_map);
}
void r_scene_set_view(r_scene *scene, m4 m)
{
scene->view_matrix = m;
scene->view_matrix_inverse = inverse(m);
}
void r_scene_set_projection(r_scene *scene, m4 m)
{
scene->projection_matrix = m;
scene->projection_matrix_inverse = inverse(m);
}
void r_update_lights(r_scene *scene)
{
glBindBuffer(GL_UNIFORM_BUFFER, scene->gl_lights);
glBufferData(GL_UNIFORM_BUFFER, sizeof(r_light_container), &scene->lights, GL_STATIC_DRAW);
}
r_object build_basic_cube()
{
r_object object = r_object_allocate(1);
object.meshes[0] = (r_mesh*)p_alloc(sizeof(r_mesh));
*object.meshes[0] = r_mesh_build_cube();
object.mesh_material[0] = (r_material*)p_alloc(sizeof(r_material));
memset(object.mesh_material[0], 0, sizeof(r_material));
object.mesh_material[0]->shader = &r_render_state.shader_pbr;
object.mesh_material[0]->albedo_factor = {1,1,1,1};
object.mesh_material[0]->emissive_factor = {1,1,1,0};
return object;
}
void r_render_scene(r_scene *scene)
{
// Render shadow maps
scene->shadow_map = r_build_shadow_map_sun(scene, &scene->lights.sun_lights[0]);
// Render environment map
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
static r_object cube = build_basic_cube();
cube.position = extract_column(scene->view_matrix_inverse, 3).xyz;
glUseProgram(r_render_state.shader_environment_map.id);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, scene->environment_map.gl_id);
glUniform1i(r_render_state.shader_environment_map.has_environment_map, 1);
glUniform1i(r_render_state.shader_environment_map.environment_map , 0);
r_render_object(scene, &cube, &r_render_state.shader_environment_map);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
// Render objects
glUseProgram(r_render_state.shader_pbr.id);
// Set shadow map
// @Cleanup: this should be in r_render_objects
glActiveTexture(GL_TEXTURE5);
glBindTexture(GL_TEXTURE_2D, scene->shadow_map.gl_depth_texture);
glUniform1i (r_render_state.shader_pbr.has_shadow_map, 1);
glUniform1i (r_render_state.shader_pbr.shadow_map , 5);
glUniformMatrix4fv(r_render_state.shader_pbr.shadow_matrix , 1, GL_TRUE, (const f32 *)(scene->shadow_map.view_matrix.E));
// Environment map
glActiveTexture(GL_TEXTURE6);
glBindTexture(GL_TEXTURE_CUBE_MAP, scene->environment_map.gl_id);
glUniform1i(r_render_state.shader_pbr.has_environment_map, 1);
glUniform1i(r_render_state.shader_pbr.environment_map , 6);
// Render objects
for(u32 i = 0; i < scene->objects_count; i++)
{
r_render_object(scene, &scene->objects[i]);
}
glUniform1i(r_render_state.shader_pbr.has_shadow_map, 0);
glUniform1i(r_render_state.shader_pbr.has_environment_map, 0);
r_free_shadow_map(&scene->shadow_map);
}
// Single object rendering functions (batched funcs are below)
void r_render_object(r_scene *scene, r_object *object, r_shader *shader_override)
{
// @Performance: Minimize draw calls by batching mesh rendering
for(u32 i = 0; i < object->count; i++)
{
r_mesh *mesh = object->meshes[i];
r_material *material = object->mesh_material[i];
r_shader *shader = material->shader;
if(shader_override)
shader = shader_override;
glUseProgram(shader->id);
glUniform1f(shader->time , (f32)r_render_state.time );
glUniform1f(shader->width , (f32)r_render_state.width );
glUniform1f(shader->height, (f32)r_render_state.height);
// Transform matrices
m4 projview_matrix = scene->projection_matrix * scene->view_matrix;
m4 projview_matrix_inverse = inverse(projview_matrix);
glUniformMatrix4fv(shader->view_matrix, 1, GL_TRUE, (const f32 *)(&projview_matrix));
glUniformMatrix4fv(shader->view_matrix_inverse, 1, GL_TRUE, (const f32 *)(&projview_matrix_inverse));
m4 model_transform = r_object_mesh_transform_matrix(object, i);
glUniformMatrix4fv(shader->model_matrix, 1, GL_TRUE, (const f32 *)(&model_transform));
// Textures
// Albedo
glUniform1i(shader->has_albedo_texture, material->albedo_texture != NULL);
if(material->albedo_texture)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, material->albedo_texture->gl_id);
glUniform1i(shader->albedo_texture, 0);
}
glUniform4f(shader->albedo_factor, material->albedo_factor.r, material->albedo_factor.g, material->albedo_factor.b, material->albedo_factor.a);
// Metallic
glUniform1i(shader->has_metallic_texture, material->metallic_texture != NULL);
if(material->metallic_texture)
{
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, material->metallic_texture->gl_id);
glUniform1i(shader->metallic_texture, 1);
}
glUniform1f(shader->metallic_factor, material->metallic_factor);
// Roughness
glUniform1i(shader->has_roughness_texture, material->roughness_texture != NULL);
if(material->roughness_texture)
{
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, material->roughness_texture->gl_id);
glUniform1i(shader->roughness_texture, 2);
}
glUniform1f(shader->roughness_factor, material->roughness_factor);
// Normal
glUniform1i(shader->has_normal_texture, material->normal_texture != NULL);
if(material->normal_texture)
{
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, material->normal_texture->gl_id);
glUniform1i(shader->normal_texture, 3);
}
// Emissive
glUniform1i(shader->has_emissive_texture, material->emissive_texture != NULL);
if(material->emissive_texture)
{
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, material->emissive_texture->gl_id);
glUniform1i(shader->emissive_texture, 4);
}
glUniform4f(shader->emissive_factor, material->emissive_factor.r, material->emissive_factor.g, material->emissive_factor.b, material->emissive_factor.a);
// Draw call
glBindVertexArray(mesh->gl_VAO);
glDrawElements(GL_TRIANGLES, mesh->indices_count, GL_UNSIGNED_INT, (void*)(0));
}
}
void r_render_object_wireframe(r_scene *scene, r_object *object)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
r_render_object(scene, object);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
void r_render_line(r_scene *scene, v3 a, v3 b, v4 a_color, v4 b_color, f32 thickness)
{
m4 projview_matrix = scene->projection_matrix * scene->view_matrix;
v4 a_trans = projview_matrix * V4(a, 1);
v4 b_trans = projview_matrix * V4(b, 1);
a_trans /= a_trans.w;
b_trans /= b_trans.w;
if(a_trans.z > 1 || b_trans.z > 1)
return;
v2 a_2d = a_trans.xy;
v2 b_2d = b_trans.xy;
a_2d = (v2{.5,.5} + v2{.5,-0.5} * a_2d) * v2{(f32)r_render_state.width, (f32)r_render_state.height};
b_2d = (v2{.5,.5} + v2{.5,-0.5} * b_2d) * v2{(f32)r_render_state.width, (f32)r_render_state.height};
r_2d_immediate_segment(a_2d, b_2d, a_color, b_color, thickness); // @Cleanup: This should be independent from the 2D drawing functions, probably.
}
// Batch render functions
void r_render_objects(r_scene *scene, u64 count, r_object **objects)
{
// @Feature: implement
}
r_shadow_map r_build_shadow_map_sun(r_scene *scene, r_sun_light *sun)
{
glCullFace(GL_FRONT);
r_shadow_map sm;
u32 width = 2*1024;
u32 height = 2*1024;
// Init depth texture/framebuffer
glGenTextures(1, &sm.gl_depth_texture);
glBindTexture(GL_TEXTURE_2D, sm.gl_depth_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &sm.gl_FBO);
glBindFramebuffer(GL_FRAMEBUFFER, sm.gl_FBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, sm.gl_depth_texture, 0);
glDrawBuffer(GL_NONE);
glBindTexture(GL_TEXTURE_2D, 0);
// Compute light frustum
m4 projview_matrix = scene->projection_matrix * scene->view_matrix;
m4 projview_matrix_inverse = inverse(projview_matrix);
v4 center_point_4 = projview_matrix_inverse * v4{0, 0, 0, 1};
v3 center_point = center_point_4.xyz / center_point_4.w;
f32 size = 1;
v4 bottom = projview_matrix_inverse * v4{ 0,-1, 0, 1};
v4 top = projview_matrix_inverse * v4{ 0, 1, 0, 1};
v4 left = projview_matrix_inverse * v4{-1, 0, 0, 1};
v4 right = projview_matrix_inverse * v4{ 1, 0, 0, 1};
v4 front = projview_matrix_inverse * v4{ 0, 0,-1, 1};
v4 back = projview_matrix_inverse * v4{ 0, 0, 1, 1};
size = maximum(size, length(top.xyz/top.w - bottom.xyz/bottom.w));
size = maximum(size, length(right.xyz/right.w - left.xyz/left.w));
size = maximum(size, length(back.xyz/back.w - front.xyz/front.w));
size /= 2; // @Cleanup: hack to have better precision. There are multiple ways to do it right: smallest box around all objects, cascaded shadow maps
v3 light_position = center_point - size * sun->direction;
m4 light_camera_matrix = r_view_matrix(light_position, sun->direction, v3{0, 0, 1});
m4 projection_matrix = r_orthographic_matrix(-size/2, size/2, -size/2, size/2, 0.1, 100.0);
sm.view_matrix = projection_matrix * light_camera_matrix;
// Render shadow map
glViewport(0, 0, width, height);
glClear(GL_DEPTH_BUFFER_BIT);
// @Feature @Cleanup: Some objects might not need to be drawn. Add a "hidden" flag. Maybe take the list of objects to render as an argument
m4 old_view = scene->view_matrix;
m4 old_proj = scene->projection_matrix;
r_scene_set_view (scene, sm.view_matrix);
r_scene_set_projection(scene, m4_identity);
for(u32 i = 0; i < scene->objects_count; i++)
{
if(scene->objects[i].has_shadow)
{
r_render_object(scene, &scene->objects[i], &r_render_state.shader_shadow_map);
}
}
r_scene_set_view (scene, old_view);
r_scene_set_projection(scene, old_proj);
// Restore framebuffer
r_framebuffer_select(r_render_state.current_framebuffer);
glCullFace(GL_BACK);
return sm;
}
void r_free_shadow_map(r_shadow_map *sm)
{
glDeleteTextures(1, &sm->gl_depth_texture);
glDeleteFramebuffers(1, &sm->gl_FBO);
}
void r_merge_and_postprocess()
{
glDisable(GL_DEPTH_TEST);
glEnable(GL_FRAMEBUFFER_SRGB);
glUseProgram(r_render_state.shader_postprocessing.id);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, r_render_state.framebuffer_3D.color_texture.gl_id);
glUniform1i(r_render_state.shader_postprocessing.texture[0], 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, r_render_state.framebuffer_HUD.color_texture.gl_id);
glUniform1i(r_render_state.shader_postprocessing.texture[1], 1);
glBindVertexArray(r_render_state.gl_screen_quad_VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glEnable(GL_DEPTH_TEST);
glDisable(GL_FRAMEBUFFER_SRGB);
}
void r_framebuffer_select(r_framebuffer *fb)
{
glBindFramebuffer(GL_FRAMEBUFFER, fb->gl_id);
glViewport(0, 0, fb->size.x, fb->size.y);
glUseProgram(r_render_state.shader_2d.id);
glUniform1i(r_render_state.shader_2d.width , fb->size.x);
glUniform1i(r_render_state.shader_2d.height, fb->size.y);
r_render_state.current_framebuffer = fb;
}

90
code/render/render.h Normal file
View File

@@ -0,0 +1,90 @@
// 2d: Functions to render 2d things immediatly. Simple things like meshes, lines, triangles, quads.
// render: Renderer, takes complex objects with materials and renders them.
// Might use an object buffer/queue and render things later, to enable
// sorting and other complex rendering procedures (culling).
// shader: Shaders! They have enough code to have their own source files
// object: Objects, materials, maybe other things. To be used by the renderer.
// light: Lights to be used by the renderer. There are enought types to separate them from objects.
// Might (or might not) add: compute shaders, vfx/postprocessing passes, animations
#ifndef _PIUMA_RENDER_RENDER_H_
#define _PIUMA_RENDER_RENDER_H_
#include "primitives.h"
#include "shader.h"
#include "2d.h"
#include "lights.h"
#include "object.h"
#include "state.h"
void r_init();
void r_deinit();
void r_size_update(u32 width, u32 height);
void r_time_update(f64 time);
void r_clear(v4 color = {0.0, 0.0, 0.0, 0.0});
void r_swap();
struct r_shadow_map
{
// FBO + depth
u32 gl_FBO;
u32 gl_depth_texture;
m4 view_matrix;
};
struct r_scene
{
m4 view_matrix;
m4 view_matrix_inverse;
m4 projection_matrix;
m4 projection_matrix_inverse;
r_object *objects;
u64 objects_count;
r_light_container lights;
GLuint gl_lights;
r_shadow_map shadow_map;
r_cubemap environment_map;
};
r_scene r_scene_create();
void r_scene_destroy(r_scene *scene);
void r_scene_set_view (r_scene *scene, m4 m);
void r_scene_set_projection(r_scene *scene, m4 m);
void r_update_lights(r_scene *scene);
void r_render_scene(r_scene *scene);
// Single object rendering functions (batched funcs are below)
void r_render_object(r_scene *scene, r_object *object, r_shader *shader_override = NULL);
void r_render_object_wireframe(r_scene *scene, r_object *object);
void r_render_line(r_scene *scene, v3 a, v3 b, v4 a_color, v4 b_color, f32 thickness = 1.0);
// Batch render functions
void r_render_objects(r_scene *scene, u64 count, r_object **objects);
// Shadow maps
r_shadow_map r_build_shadow_map_sun(r_scene *scene, r_sun_light *sun);
void r_free_shadow_map(r_shadow_map *sm);
void r_framebuffer_select(r_framebuffer *fb);
void r_merge_and_postprocess();
#endif

193
code/render/shader.cpp Normal file
View File

@@ -0,0 +1,193 @@
#include "shader.h"
#include "../debug/logger.h"
#define STB_INCLUDE_IMPLEMENTATION
#define STB_INCLUDE_LINE_GLSL
#include "stb_include.h"
// Internal functions
void r_shader_set_uniform_locations(r_shader *shader);
bool r_shader_from_files(r_shader *shader, const char *vertex, const char *fragment, const char *geometry, const char *tesseletion_control, const char *tesseletion_evaluation, const char *compute)
{
const int count = 6;
char *source[count] = {NULL, NULL, NULL, NULL, NULL, NULL};
const char *filename[count] = {vertex, fragment, geometry, tesseletion_control, tesseletion_evaluation, compute};
const char *type[count] = {"vertex", "fragment", "geometry", "tesseletion_control", "tesseletion_evaluation", "compute"};
// Load sources
bool has_error = false;
char error[256];
for(int i = 0; i < count; i++)
{
if(filename[i])
{
char path[4096];
u32 len = strlen(filename[i]);
memcpy(path, filename[i], len+1);
int last_slash = len;
while(last_slash > 0)
{
if(filename[i][last_slash] == '/')
break;
last_slash--;
}
path[last_slash] = '\0';
source[i] = stb_include_file((char*)filename[i], NULL, path, error);
if(!source[i])
{
has_error = true;
LOG(LOG_ERROR, "Error loading %s shader: %s", type[i], error);
break;
}
}
}
// Compile shader
if(!has_error)
{
has_error = !r_shader_from_text(shader, source[0], source[1], source[2], source[3], source[5], source[5]);
}
// Cleanup
for(int i = 0; i < count; i++)
{
if(source[i])
free(source[i]);
}
return !has_error;
}
bool r_shader_from_text(r_shader *shader, const char *vertex, const char *fragment, const char *geometry, const char *tesseletion_control, const char *tesseletion_evaluation, const char *compute)
{
int count = 6;
GLuint type[count] = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER, GL_GEOMETRY_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_COMPUTE_SHADER};
const char *source[count] = {vertex, fragment, geometry, tesseletion_control, tesseletion_evaluation, compute};
// Create shader program
shader->id = glCreateProgram();
bool has_error = false;
for(int i = 0; i < count; i++)
{
if(!source[i])
continue;
// Compile current shader source
GLuint source_id = glCreateShader(type[i]);
GLint size = strlen(source[i]);
glShaderSource(source_id, 1, &source[i], &size);
glCompileShader(source_id);
// Check for success/errors
char build_info[2048];
GLint build_success;
glGetShaderiv(source_id, GL_COMPILE_STATUS, &build_success);
if (!build_success)
{
glGetShaderInfoLog(source_id, 2048, NULL, build_info);
LOG(LOG_ERROR, "Cannot compile shader %.*s: %s", (int)size, source, build_info);
glDeleteShader(source_id);
has_error = true;
break;
}
// Attach compiler shader to program
glAttachShader(shader->id, source_id);
}
// @Correctness: Clean up shader sources after fail
if(has_error)
return false;
// Link program
glLinkProgram(shader->id);
// Check for success/error
GLint build_success;
char build_info[512];
glGetProgramiv(shader->id, GL_LINK_STATUS, &build_success);
if(!build_success) {
glGetProgramInfoLog(shader->id, 512, NULL, build_info);
LOG(LOG_ERROR, "Cannot link shader program: %s", build_info);
glDeleteShader(shader->id);
has_error = true;
}
// Load uniform locations
if(!has_error)
r_shader_set_uniform_locations(shader);
return !has_error;
}
void r_shader_delete(r_shader *shader)
{
glDeleteProgram(shader->id);
}
// Internal functions
void r_shader_set_uniform_locations(r_shader *shader)
{
shader->time = glGetUniformLocation(shader->id, "time");
shader->width = glGetUniformLocation(shader->id, "width");
shader->height = glGetUniformLocation(shader->id, "height");
shader->view_matrix = glGetUniformLocation(shader->id, "view_matrix");
shader->view_matrix_inverse = glGetUniformLocation(shader->id, "view_matrix_inverse");
shader->model_matrix = glGetUniformLocation(shader->id, "model_matrix");
shader->camera_position = glGetUniformLocation(shader->id, "camera_position");
for(int i = 0; i < R_SHADER_COLOR_MAX; i++)
{
char uniform_name[32]; sprintf(uniform_name, "color%d", i);
shader->color[i] = glGetUniformLocation(shader->id, uniform_name);
}
for(int i = 0; i < R_SHADER_TEXTURES_MAX; i++)
{
char uniform_name[32];
sprintf(uniform_name, "has_texture%d", i);
shader->has_texture [i] = glGetUniformLocation(shader->id, uniform_name);
sprintf(uniform_name, "texture%d", i);
shader->texture [i] = glGetUniformLocation(shader->id, uniform_name);
sprintf(uniform_name, "texture_channels%d", i);
shader->texture_channels[i] = glGetUniformLocation(shader->id, uniform_name);
}
shader->lights = glGetUniformBlockIndex(shader->id, "lights");
if(shader->lights != GL_INVALID_INDEX)
glUniformBlockBinding(shader->id, shader->lights, 0);
shader->has_shadow_map = glGetUniformLocation(shader->id, "has_shadow_map");
shader->shadow_map = glGetUniformLocation(shader->id, "shadow_map");
shader->shadow_matrix = glGetUniformLocation(shader->id, "shadow_matrix");
shader->has_environment_map = glGetUniformLocation(shader->id, "has_environment_map");
shader->environment_map = glGetUniformLocation(shader->id, "environment_map");
shader->has_albedo_texture = glGetUniformLocation(shader->id, "has_albedo_texture");
shader->albedo_texture = glGetUniformLocation(shader->id, "albedo_texture");
shader->albedo_factor = glGetUniformLocation(shader->id, "albedo_factor");
shader->has_metallic_texture = glGetUniformLocation(shader->id, "has_metallic_texture");
shader->metallic_texture = glGetUniformLocation(shader->id, "metallic_texture");
shader->metallic_factor = glGetUniformLocation(shader->id, "metallic_factor");
shader->has_roughness_texture = glGetUniformLocation(shader->id, "has_roughness_texture");
shader->roughness_texture = glGetUniformLocation(shader->id, "roughness_texture");
shader->roughness_factor = glGetUniformLocation(shader->id, "roughness_factor");
shader->has_normal_texture = glGetUniformLocation(shader->id, "has_normal_texture");
shader->normal_texture = glGetUniformLocation(shader->id, "normal_texture");
shader->has_emissive_texture = glGetUniformLocation(shader->id, "has_emissive_texture");
shader->emissive_texture = glGetUniformLocation(shader->id, "emissive_texture");
shader->emissive_factor = glGetUniformLocation(shader->id, "emissive_factor");
}

85
code/render/shader.h Normal file
View File

@@ -0,0 +1,85 @@
#ifndef _PIUMA_RENDER_SHADER_H_
#define _PIUMA_RENDER_SHADER_H_
#include "../lib/types.h"
#include "GL/glcorearb.h"
/*
I put the uniform ids of all the shaders in the r_shader structure. Then I use only the
ones I need. Yeah, it's not very flexible.
I would like to have automatic syncronization between shader and C++ code.
There are 3 ways to do that:
- Parse shader code and auto-generate C++ structures and code;
- Parse C++ structures and generate a shader source, or update an existing one;
- Use a language than let me define shader code directly in that language, then it's compiled
directly to GPU ASM, or it generates GLSL code that is then given to the GPU driver. This is
the ideal solution, but yeah, that language is not C++... if it event exists. Let's hope for
a better future.
*/
#define R_SHADER_TEXTURES_MAX 4
#define R_SHADER_COLOR_MAX 4
struct r_shader
{
GLuint id;
// Common state
GLint time;
GLint width;
GLint height;
// View and camera
GLint view_matrix;
GLint view_matrix_inverse;
GLint model_matrix;
GLint camera_position;
// For arrays, the name in the shader is in the format [name][index].
// Example: color[4] will be: color0, color1, color2, color3
// Generic parameters that can be used for different purpose based on the shader needs.
// Example: texture1 could be a diffuse texture, texture2 an emissive texture, texture3 bump mapping
GLint color[R_SHADER_COLOR_MAX];
GLint has_texture [R_SHADER_TEXTURES_MAX]; // Is textureX assigned or not?
GLint texture [R_SHADER_TEXTURES_MAX]; // Actual texture
GLint texture_channels[R_SHADER_TEXTURES_MAX]; // Number of channels in the texture data
// Lights and shadows
// @Cleanup: maybe merge this with the generic texture parameter?
GLint lights;
GLint has_shadow_map;
GLint shadow_map;
GLint shadow_matrix;
// Environment map
GLint has_environment_map;
GLint environment_map;
// PBR material
GLint has_albedo_texture;
GLint albedo_texture;
GLint albedo_factor;
GLint has_metallic_texture;
GLint metallic_texture;
GLint metallic_factor;
GLint has_roughness_texture;
GLint roughness_texture;
GLint roughness_factor;
GLint has_normal_texture;
GLint normal_texture;
GLint has_emissive_texture;
GLint emissive_texture;
GLint emissive_factor;
};
bool r_shader_from_files(r_shader *shader, const char *vertex, const char *fragment, const char *geometry = NULL, const char *tesseletion_control = NULL, const char *tesseletion_evaluation = NULL, const char *compute = NULL);
bool r_shader_from_text(r_shader *shader, const char *vertex, const char *fragment, const char *geometry = NULL, const char *tesseletion_control = NULL, const char *tesseletion_evaluation = NULL, const char *compute = NULL);
void r_shader_delete(r_shader *shader);
#endif

36
code/render/state.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef _PIUMA_RENDER_STATE_H_
#define _PIUMA_RENDER_STATE_H_
#include "shader.h"
#include "primitives.h"
struct r_state
{
// Shaders
r_shader shader_2d;
r_shader shader_postprocessing;
r_shader shader_pbr;
r_shader shader_shadow_map;
r_shader shader_environment_map;
// Screen size
u32 width, height;
// Time
f64 time;
// Framebuffers
r_framebuffer *current_framebuffer;
r_framebuffer framebuffer_SCREEN;
r_framebuffer framebuffer_HUD;
r_framebuffer framebuffer_3D;
// Quads
u32 gl_screen_quad_VAO;
u32 gl_screen_quad_VBO;
};
extern r_state r_render_state;
#endif

6
external/cgltf.cpp vendored Normal file
View File

@@ -0,0 +1,6 @@
#define CGLTF_IMPLEMENTATION
#include "cgltf.h"
#undef CGLTF_IMPLEMENTATION
#define CGLTF_WRITE_IMPLEMENTATION
#include "cgltf_write.h"

7050
external/cgltf.h vendored Normal file

File diff suppressed because it is too large Load Diff

1506
external/cgltf_write.h vendored Normal file

File diff suppressed because it is too large Load Diff

2
external/stb_image.cpp vendored Normal file
View File

@@ -0,0 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

7987
external/stb_image.h vendored Normal file

File diff suppressed because it is too large Load Diff

295
external/stb_include.h vendored Normal file
View File

@@ -0,0 +1,295 @@
// stb_include.h - v0.02 - parse and process #include directives - public domain
//
// To build this, in one source file that includes this file do
// #define STB_INCLUDE_IMPLEMENTATION
//
// This program parses a string and replaces lines of the form
// #include "foo"
// with the contents of a file named "foo". It also embeds the
// appropriate #line directives. Note that all include files must
// reside in the location specified in the path passed to the API;
// it does not check multiple directories.
//
// If the string contains a line of the form
// #inject
// then it will be replaced with the contents of the string 'inject' passed to the API.
//
// Options:
//
// Define STB_INCLUDE_LINE_GLSL to get GLSL-style #line directives
// which use numbers instead of filenames.
//
// Define STB_INCLUDE_LINE_NONE to disable output of #line directives.
//
// Standard libraries:
//
// stdio.h FILE, fopen, fclose, fseek, ftell
// stdlib.h malloc, realloc, free
// string.h strcpy, strncmp, memcpy
//
// Credits:
//
// Written by Sean Barrett.
//
// Fixes:
// Michal Klos
#ifndef STB_INCLUDE_STB_INCLUDE_H
#define STB_INCLUDE_STB_INCLUDE_H
// Do include-processing on the string 'str'. To free the return value, pass it to free()
char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]);
// Concatenate the strings 'strs' and do include-processing on the result. To free the return value, pass it to free()
char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]);
// Load the file 'filename' and do include-processing on the string therein. note that
// 'filename' is opened directly; 'path_to_includes' is not used. To free the return value, pass it to free()
char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256]);
#endif
#ifdef STB_INCLUDE_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *stb_include_load_file(char *filename, size_t *plen)
{
char *text;
size_t len;
FILE *f = fopen(filename, "rb");
if (f == 0) return 0;
fseek(f, 0, SEEK_END);
len = (size_t) ftell(f);
if (plen) *plen = len;
text = (char *) malloc(len+1);
if (text == 0) return 0;
fseek(f, 0, SEEK_SET);
fread(text, 1, len, f);
fclose(f);
text[len] = 0;
return text;
}
typedef struct
{
int offset;
int end;
char *filename;
int next_line_after;
} include_info;
static include_info *stb_include_append_include(include_info *array, int len, int offset, int end, char *filename, int next_line)
{
include_info *z = (include_info *) realloc(array, sizeof(*z) * (len+1));
z[len].offset = offset;
z[len].end = end;
z[len].filename = filename;
z[len].next_line_after = next_line;
return z;
}
static void stb_include_free_includes(include_info *array, int len)
{
int i;
for (i=0; i < len; ++i)
free(array[i].filename);
free(array);
}
static int stb_include_isspace(int ch)
{
return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
}
// find location of all #include and #inject
static int stb_include_find_includes(char *text, include_info **plist)
{
int line_count = 1;
int inc_count = 0;
char *s = text, *start;
include_info *list = NULL;
while (*s) {
// parse is always at start of line when we reach here
start = s;
while (*s == ' ' || *s == '\t')
++s;
if (*s == '#') {
++s;
while (*s == ' ' || *s == '\t')
++s;
if (0==strncmp(s, "include", 7) && stb_include_isspace(s[7])) {
s += 7;
while (*s == ' ' || *s == '\t')
++s;
if (*s == '"') {
char *t = ++s;
while (*t != '"' && *t != '\n' && *t != '\r' && *t != 0)
++t;
if (*t == '"') {
char *filename = (char *) malloc(t-s+1);
memcpy(filename, s, t-s);
filename[t-s] = 0;
s=t;
while (*s != '\r' && *s != '\n' && *s != 0)
++s;
// s points to the newline, so s-start is everything except the newline
list = stb_include_append_include(list, inc_count++, start-text, s-text, filename, line_count+1);
}
}
} else if (0==strncmp(s, "inject", 6) && (stb_include_isspace(s[6]) || s[6]==0)) {
while (*s != '\r' && *s != '\n' && *s != 0)
++s;
list = stb_include_append_include(list, inc_count++, start-text, s-text, NULL, line_count+1);
}
}
while (*s != '\r' && *s != '\n' && *s != 0)
++s;
if (*s == '\r' || *s == '\n') {
s = s + (s[0] + s[1] == '\r' + '\n' ? 2 : 1);
}
++line_count;
}
*plist = list;
return inc_count;
}
// avoid dependency on sprintf()
static void stb_include_itoa(char str[9], int n)
{
int i;
for (i=0; i < 8; ++i)
str[i] = ' ';
str[i] = 0;
for (i=1; i < 8; ++i) {
str[7-i] = '0' + (n % 10);
n /= 10;
if (n == 0)
break;
}
}
static char *stb_include_append(char *str, size_t *curlen, char *addstr, size_t addlen)
{
str = (char *) realloc(str, *curlen + addlen);
memcpy(str + *curlen, addstr, addlen);
*curlen += addlen;
return str;
}
char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename, char error[256])
{
char temp[4096];
include_info *inc_list;
int i, num = stb_include_find_includes(str, &inc_list);
size_t source_len = strlen(str);
char *text=0;
size_t textlen=0, last=0;
for (i=0; i < num; ++i) {
text = stb_include_append(text, &textlen, str+last, inc_list[i].offset - last);
// write out line directive for the include
#ifndef STB_INCLUDE_LINE_NONE
#ifdef STB_INCLUDE_LINE_GLSL
if (textlen != 0) // GLSL #version must appear first, so don't put a #line at the top
#endif
{
strcpy(temp, "#line ");
stb_include_itoa(temp+6, 1);
strcat(temp, " ");
#ifdef STB_INCLUDE_LINE_GLSL
stb_include_itoa(temp+15, i+1);
#else
strcat(temp, "\"");
if (inc_list[i].filename == 0)
strcmp(temp, "INJECT");
else
strcat(temp, inc_list[i].filename);
strcat(temp, "\"");
#endif
strcat(temp, "\n");
text = stb_include_append(text, &textlen, temp, strlen(temp));
}
#endif
if (inc_list[i].filename == 0) {
if (inject != 0)
text = stb_include_append(text, &textlen, inject, strlen(inject));
} else {
char *inc;
strcpy(temp, path_to_includes);
strcat(temp, "/");
strcat(temp, inc_list[i].filename);
inc = stb_include_file(temp, inject, path_to_includes, error);
if (inc == NULL) {
stb_include_free_includes(inc_list, num);
return NULL;
}
text = stb_include_append(text, &textlen, inc, strlen(inc));
free(inc);
}
// write out line directive
#ifndef STB_INCLUDE_LINE_NONE
strcpy(temp, "\n#line ");
stb_include_itoa(temp+6, inc_list[i].next_line_after);
strcat(temp, " ");
#ifdef STB_INCLUDE_LINE_GLSL
stb_include_itoa(temp+15, 0);
#else
strcat(temp, filename != 0 ? filename : "source-file");
#endif
text = stb_include_append(text, &textlen, temp, strlen(temp));
// no newlines, because we kept the #include newlines, which will get appended next
#endif
last = inc_list[i].end;
}
text = stb_include_append(text, &textlen, str+last, source_len - last + 1); // append '\0'
stb_include_free_includes(inc_list, num);
return text;
}
char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename, char error[256])
{
char *text;
char *result;
int i;
size_t length=0;
for (i=0; i < count; ++i)
length += strlen(strs[i]);
text = (char *) malloc(length+1);
length = 0;
for (i=0; i < count; ++i) {
strcpy(text + length, strs[i]);
length += strlen(strs[i]);
}
result = stb_include_string(text, inject, path_to_includes, filename, error);
free(text);
return result;
}
char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256])
{
size_t len;
char *result;
char *text = stb_include_load_file(filename, &len);
if (text == NULL) {
strcpy(error, "Error: couldn't load '");
strcat(error, filename);
strcat(error, "'");
return 0;
}
result = stb_include_string(text, inject, path_to_includes, filename, error);
free(text);
return result;
}
#if 0 // @TODO, GL_ARB_shader_language_include-style system that doesn't touch filesystem
char *stb_include_preloaded(char *str, char *inject, char *includes[][2], char error[256])
{
}
#endif
#endif // STB_INCLUDE_IMPLEMENTATION

2
external/stb_truetype.cpp vendored Normal file
View File

@@ -0,0 +1,2 @@
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"

5077
external/stb_truetype.h vendored Normal file

File diff suppressed because it is too large Load Diff

2
external/stb_vorbis.cpp vendored Normal file
View File

@@ -0,0 +1,2 @@
#define STB_VORBIS_IMPLEMENTATION
#include "stb_vorbis.h"

5470
external/stb_vorbis.h vendored Normal file

File diff suppressed because it is too large Load Diff

38
shaders/2d.frag Normal file
View File

@@ -0,0 +1,38 @@
#version 430 core
in Fragment
{
// @Performance: Are this fields aligned to vec4? Is it better to reorder them?
vec2 position;
vec4 color;
vec2 uv;
} frag;
uniform int width; // Width of the screen in pixels
uniform int height; // Height of the screen in pixels
uniform int has_texture0;
uniform sampler2D texture0;
uniform int texture_channels0;
uniform vec4 color0;
out vec4 FragColor;
void main()
{
FragColor = frag.color;
if(has_texture0 != 0)
{
vec4 texture_color;
if(texture_channels0 == 1)
texture_color = vec4(1.0, 1.0, 1.0, texture(texture0, frag.uv).r);
else if(texture_channels0 == 3)
texture_color = vec4(texture(texture0, frag.uv).rgb, 1.0);
else
texture_color = texture(texture0, frag.uv);
FragColor = color0 * frag.color * texture_color;
FragColor.rgb *= FragColor.a; // Premultiplied alpha
}
}

30
shaders/2d.vert Normal file
View File

@@ -0,0 +1,30 @@
#version 430 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec4 color;
layout (location = 2) in vec2 uv;
out Fragment
{
// @Performance: Are this fields aligned to vec4? Is it better to reorder them?
vec2 position;
vec4 color;
vec2 uv;
} frag;
uniform int width; // Width of the screen in pixels
uniform int height; // Height of the screen in pixels
void main()
{
frag.position = position;
frag.color = color;
frag.uv = uv;
vec2 screen = vec2(width, height);
vec2 p = position / screen * 2.0 - 1.0; // Position relative to the screen size, in range [-1,+1)
p.y = -p.y; // OpenGL coordinates have y up, pixel coordinates have y down
gl_Position = vec4(p, 0.0, 1.0);
}

View File

@@ -0,0 +1,13 @@
#version 430 core
in vec3 frag_position;
out vec4 FragColor;
uniform samplerCube environment_map;
void main()
{
FragColor = texture(environment_map, frag_position.xzy);
}

View File

@@ -0,0 +1,18 @@
#version 430 core
layout (location = 0) in vec3 position;
out vec3 frag_position;
uniform mat4 view_matrix;
uniform mat4 view_matrix_inverse;
uniform mat4 model_matrix;
uniform float time;
void main()
{
frag_position = position;
vec4 world_position = model_matrix * vec4(position, 1.0);
gl_Position = view_matrix * world_position;
}

320
shaders/pbr.frag Normal file
View File

@@ -0,0 +1,320 @@
#version 430 core
const float PI = 3.141592653589793238462643383;
const float TAU = 6.283185307179586476925286766;
const float EPSILON = 0.000001;
in vec3 frag_position;
in vec3 frag_normal;
in vec3 frag_tangent;
in vec3 frag_bitangent;
in vec2 frag_texture_coord;
in vec4 view_position;
out vec4 FragColor;
uniform int has_albedo_texture;
uniform sampler2D albedo_texture;
uniform vec4 albedo_factor;
uniform int has_metallic_texture;
uniform sampler2D metallic_texture;
uniform float metallic_factor;
uniform int has_roughness_texture;
uniform sampler2D roughness_texture;
uniform float roughness_factor;
uniform int has_normal_texture;
uniform sampler2D normal_texture;
uniform int has_emissive_texture;
uniform sampler2D emissive_texture;
uniform vec4 emissive_factor;
uniform mat4 view_matrix;
uniform mat4 view_matrix_inverse;
uniform mat4 model_matrix;
uniform int has_shadow_map;
uniform sampler2DShadow shadow_map;
uniform mat4 shadow_matrix;
uniform int has_environment_map;
uniform samplerCube environment_map;
struct SunLight
{
vec3 direction;
float _padding0;
vec3 color;
float intensity;
};
struct PointLight
{
vec3 position;
float _padding0;
vec3 color;
float intensity;
};
struct SpotLight
{
vec3 position;
float inner_radius;
vec3 color;
float intensity;
vec3 direction;
float outer_radius;
};
#define MAX_SUN_LIGHTS 4
#define MAX_POINT_LIGHTS 128
#define MAX_SPOT_LIGHTS 128
layout (std140) uniform lights
{
uint sun_light_count;
uint point_light_count;
uint spot_light_count;
float ambient_light;
SunLight sun_lights[MAX_SUN_LIGHTS];
PointLight point_lights[MAX_POINT_LIGHTS];
SpotLight spot_lights[MAX_SPOT_LIGHTS];
};
struct MaterialInfo
{
vec4 Albedo;
float Metallic;
float Roughness;
vec4 Emissive;
};
uniform float time;
uniform float width;
uniform float height;
float clamped_dot(vec3 v, vec3 w)
{
return max( dot(v, w) , 0.0);
}
vec3 DiffuseBRDF(MaterialInfo material, vec3 L, vec3 V)
{
// Lambertian
return material.Albedo.xyz / PI;
}
float SpecularNDF(MaterialInfo material, vec3 N, vec3 H)
{
// GGX
float a = material.Roughness * material.Roughness;
float a2 = a * a;
float n_dot_h = clamped_dot(N, H);
float n_dot_h2 = n_dot_h * n_dot_h;
float d = n_dot_h2 * (a2 - 1.0) + 1.0;
return a2 / (PI * d*d + EPSILON);
}
float SpecularGeometricAttenuation(MaterialInfo material, vec3 N, vec3 L, vec3 V, vec3 H)
{
// Schlick
float r = material.Roughness + 1;
float k = r*r / 8.0;
float n_dot_v = clamped_dot(N, V);
float n_dot_l = clamped_dot(N, L);
float denom_v = n_dot_v * (1.0 - k) + k;
float denom_l = n_dot_l * (1.0 - k) + k;
return (n_dot_v / denom_v) * (n_dot_l / denom_l);
}
vec3 SpecularFresnel(vec3 F0, vec3 V, vec3 H)
{
// Schlick, Epic paper
float v_dot_h = clamped_dot(V, H);
float exp = (-5.55473 * v_dot_h - 6.98316) * v_dot_h;
return F0 + (1.0 - F0) * pow(2, exp);
}
vec3 BRDF(MaterialInfo material, vec3 radiance, vec3 N, vec3 L, vec3 V, vec3 H)
{
// Cook-Torrance
vec3 F0 = mix(vec3(0.04), material.Albedo.xyz, material.Metallic);
float D = SpecularNDF(material, N, H);
vec3 F = SpecularFresnel(F0, N, H);
float G = SpecularGeometricAttenuation(material, N, L, V, H);
float n_dot_v = clamped_dot(N, V);
float n_dot_l = clamped_dot(N, L);
float specular_denom = (4.0 * n_dot_l * n_dot_v) + EPSILON;
vec3 specular = D * F * G / specular_denom;
vec3 diffuse = (1.0 - F) * DiffuseBRDF(material, L, V);
return (diffuse + specular) * radiance * n_dot_l;
}
void main()
{
vec4 final_color = vec4(0,0,0,1);
// PBR Parameters
MaterialInfo material;
material.Albedo = albedo_factor;
if(has_albedo_texture != 0)
material.Albedo = albedo_factor * texture(albedo_texture, frag_texture_coord);
material.Metallic = metallic_factor;
if(has_metallic_texture != 0)
material.Metallic = metallic_factor * texture(metallic_texture, frag_texture_coord).b;
material.Roughness = roughness_factor;
if(has_roughness_texture != 0)
material.Roughness = roughness_factor * texture(roughness_texture, frag_texture_coord).g;
material.Emissive = emissive_factor;
if(has_emissive_texture != 0)
material.Emissive = emissive_factor * texture(emissive_texture, frag_texture_coord);
vec3 Normal = normalize(frag_normal);
if(has_normal_texture != 0)
{
vec3 normal_map = normalize(texture(normal_texture, frag_texture_coord).rgb * 2.0 - 1.0);
mat3 TBN = mat3(normalize(frag_tangent), normalize(frag_bitangent), normalize(frag_normal));
Normal = normalize(TBN * normal_map);
}
vec4 camera_position_4 = view_matrix_inverse * vec4(view_position.xy / view_position.w, 0, 1);
vec3 camera_position = camera_position_4.xyz / camera_position_4.w;
// vec4 pixel_position_4 = view_matrix_inverse * view_position;
// vec3 pixel_position = pixel_position_4.xyz / pixel_position_4.w;
// vec3 view_direction = normalize(pixel_position - camera_position);
vec3 view_direction = normalize(frag_position - camera_position);
vec3 V = -view_direction;
vec3 N = Normal;
// Sun lights
for(uint i = 0; i < sun_light_count; i++)
{
SunLight light = sun_lights[i];
float shadow = 1.0;
if(has_shadow_map != 0)
{
shadow = 0;
vec2 texel_size = 1.0 / textureSize(shadow_map, 0);
float bias = 0.0001;
vec4 from_light_view = shadow_matrix * vec4(frag_position, 1.0);
from_light_view /= from_light_view.w;
from_light_view = from_light_view * 0.5 + 0.5; // [-1,1] => [0,1] uv coords
from_light_view.z = from_light_view.z - bias; // Bias to attenuate z fighting
for(int x = -1; x <= 1; x++)
for(int y = -1; y <= 1; y++)
{
float s = texture(shadow_map, from_light_view.xyz + vec3(x * texel_size.x, y * texel_size.y, 0));
s = clamp(s, 0.0, 1.0);
shadow += s;
}
shadow /= 9;
}
vec3 L = -light.direction;
vec3 H = normalize(V + L);
vec3 radiance = light.color * light.intensity * shadow;
vec3 color = BRDF(material, radiance, N, L, V, H);
final_color.xyz += color;
}
// Point lights
for(uint i = 0; i < point_light_count; i++)
{
PointLight light = point_lights[i];
vec3 diff = light.position - frag_position;
float dist2 = abs(dot(diff, diff));
float attenuation = 1.0 / (dist2 + EPSILON);
float intensity = light.intensity * attenuation;
vec3 L = normalize(light.position - frag_position);
vec3 H = normalize(V + L);
vec3 radiance = light.color * intensity;
vec3 color = BRDF(material, radiance, N, L, V, H);
final_color.xyz += color;
}
// Spot lights
for(uint i = 0; i < spot_light_count; i++)
{
SpotLight light = spot_lights[i];
vec3 light_direction = normalize(frag_position - light.position);
float dist2 = abs(dot(light.position, frag_position));
float attenuation = 1.0 / (dist2 + EPSILON);
float intensity = light.intensity * attenuation;
float angle = acos(dot(light.direction, light_direction));
float spot_factor = (angle - light.outer_radius) / (light.inner_radius - light.outer_radius);
spot_factor = clamp(spot_factor, 0.0, 1.0);
intensity *= spot_factor;
vec3 L = normalize(light.position - frag_position);
vec3 H = normalize(V + L);
vec3 radiance = light.color * intensity;
vec3 color = BRDF(material, radiance, N, L, V, H);
final_color.xyz += color;
}
// Environment map light
if(has_environment_map != 0)
{
vec3 L = normalize(reflect(-V, N));
vec3 H = N;//normalize(V + L);
float levels = textureQueryLevels(environment_map);
vec3 radiance = textureLod(environment_map, L.xzy, material.Roughness * levels).rgb;
//vec3 color = BRDF(material, radiance, N, L, V, H);
vec3 F0 = mix(vec3(0.04), material.Albedo.xyz, material.Metallic);
float D = SpecularNDF(material, N, H);
vec3 F = SpecularFresnel(F0, N, H);
float G = SpecularGeometricAttenuation(material, N, L, V, H);
float n_dot_v = clamped_dot(N, V);
float n_dot_l = clamped_dot(N, L);
float specular_denom = (4.0 * n_dot_l * n_dot_v) + EPSILON;
vec3 specular = D * F * G / specular_denom;
vec3 diffuse = (1.0 - F) * DiffuseBRDF(material, L, V);
vec3 color = (diffuse /*+ specular*/) * radiance * n_dot_l;
final_color.xyz += color;
}
// Ambient light
final_color.xyz += ambient_light * material.Albedo.xyz;
final_color.a = material.Albedo.a;
// Emissive color
final_color.xyz += material.Emissive.xyz * material.Emissive.a;
FragColor = final_color;
//FragColor.xyz = Normal;
}

34
shaders/pbr.vert Normal file
View File

@@ -0,0 +1,34 @@
#version 430 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec3 tangent;
layout (location = 3) in vec2 texture_coord;
out vec3 frag_position;
out vec3 frag_normal;
out vec3 frag_tangent;
out vec3 frag_bitangent;
out vec2 frag_texture_coord;
out vec4 view_position;
uniform mat4 view_matrix;
uniform mat4 view_matrix_inverse;
uniform mat4 model_matrix;
uniform float time;
uniform mat4 shadow_matrix;
void main()
{
mat3 model_inverse_matrix = mat3(transpose(inverse(model_matrix))); // @Performance: Compute this only once, before calling the shader
frag_normal = normalize(model_inverse_matrix * normal );
frag_tangent = normalize(model_inverse_matrix * tangent);
frag_bitangent = normalize(model_inverse_matrix * cross(normal, tangent));
vec4 world_position = model_matrix * vec4(position, 1.0);
frag_position = world_position.xyz / world_position.w;
frag_texture_coord = texture_coord;
gl_Position = view_matrix * world_position;
view_position = gl_Position;
}

View File

@@ -0,0 +1,23 @@
#version 430 core
out vec4 FragColor;
in vec2 frag_uv;
uniform sampler2D texture0; // rendered 3D
uniform sampler2D texture1; // rendered HUD
void main()
{
vec3 hdr_color = texture(texture0, frag_uv).rgb;
// HDR - TODO
vec3 sdr_color = min(hdr_color, 1.0);
vec4 hud_color = texture(texture1, frag_uv);
vec3 mixed = mix(sdr_color, hud_color.rgb, hud_color.a); // TODO: SDR color
FragColor = vec4(mixed, 1.0);
}

View File

@@ -0,0 +1,11 @@
#version 430 core
layout (location = 0) in vec2 position;
layout (location = 1) in vec2 uv;
out vec2 frag_uv;
void main()
{
frag_uv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}

5
shaders/shadow_map.frag Normal file
View File

@@ -0,0 +1,5 @@
#version 430 core
void main()
{
}

1
shaders/shadow_map.vert Normal file
View File

@@ -0,0 +1 @@
#include "pbr.vert"