392 lines
12 KiB
C
392 lines
12 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
|
|
// #define ARENA_IMPLEMENTATION
|
|
// #include <tsoding/arena.h>
|
|
|
|
#define SOKOL_DEBUG
|
|
|
|
#define SOKOL_APP_IMPL
|
|
#define SOKOL_GFX_IMPL
|
|
#define SOKOL_GLUE_IMPL
|
|
#define SOKOL_FETCH_IMPL
|
|
#define SOKOL_LOG_IMPL
|
|
|
|
// TODO: condition compilation
|
|
// #define SOKOL_METAL
|
|
#define SOKOL_GLCORE33
|
|
|
|
#include <sokol/sokol_log.h>
|
|
#include <sokol/sokol_gfx.h>
|
|
#include <sokol/sokol_app.h>
|
|
#include <sokol/sokol_glue.h>
|
|
#include <sokol/sokol_fetch.h>
|
|
|
|
#define STB_TRUETYPE_IMPLEMENTATION
|
|
#include <stb/std_truetype.h>
|
|
|
|
#define ED_STRING_IMPLEMENTATION
|
|
#define ED_HT_IMPLEMENTATION
|
|
#define ED_UI_IMPLEMENTATION
|
|
#include "string.h"
|
|
#include "ht.h"
|
|
#include "ui.h"
|
|
#include "ed_array.h"
|
|
|
|
|
|
// static Arena default_arena = {0};
|
|
// static Arena temporary_arena = {0};
|
|
// static Arena *context_arena = &default_arena;
|
|
|
|
void *context_alloc(size_t size) {
|
|
// assert(context_arena);
|
|
// return arena_alloc(context_arena, size);
|
|
return malloc(size);
|
|
}
|
|
|
|
typedef struct {
|
|
float position[2];
|
|
float size[2];
|
|
float border_size[2];
|
|
} GpuUiRect;
|
|
arrayTemplate(GpuUiRect);
|
|
|
|
typedef struct {
|
|
float atlas_position[2];
|
|
float size[2];
|
|
float position[2];
|
|
float y_offset;
|
|
} GpuGlyph;
|
|
arrayTemplate(GpuGlyph);
|
|
|
|
typedef struct {
|
|
float screen_size[4];
|
|
} GpuUniformParams;
|
|
|
|
static struct {
|
|
sg_pass_action pass_action;
|
|
sg_pipeline pip;
|
|
sg_bindings bind;
|
|
|
|
sg_image text_atlas_image;
|
|
sg_sampler text_atlas_sampler;
|
|
|
|
sg_shader_desc scratch_shader_desc;
|
|
|
|
array(GpuUiRect) gpu_ui_rects;
|
|
array(GpuGlyph) gpu_glyphs;
|
|
array(GpuGlyph) glyph_cache;
|
|
|
|
bool should_exit;
|
|
|
|
ui_context ui_cx;
|
|
} state;
|
|
|
|
void queue_text(string text, float position[2]) {
|
|
float x = 0;
|
|
for (size_t i=0; i < text.len; ++i) {
|
|
if (text.data[i] >= 32) {
|
|
//GpuGlyph glyph = *((GpuGlyph *)(state.glyph_cache.data+((text.data[i] - 32) * sizeof(GpuGlyph))));
|
|
GpuGlyph glyph = state.glyph_cache.data[text.data[i] - 32];
|
|
|
|
glyph.position[0] = x+position[0];
|
|
glyph.position[1] = position[1];
|
|
x += glyph.size[0]/2;
|
|
|
|
pushArray(GpuGlyph, &state.gpu_glyphs, glyph);
|
|
}
|
|
}
|
|
}
|
|
|
|
void vertex_shader_loaded(const sfetch_response_t *response) {
|
|
if (response->fetched) {
|
|
state.scratch_shader_desc.vs = (sg_shader_stage_desc) {
|
|
.source = response->data.ptr,
|
|
.entry = "vs_main",
|
|
.uniform_blocks[0] = {
|
|
.size = sizeof(GpuUniformParams),
|
|
.layout = SG_UNIFORMLAYOUT_STD140,
|
|
.uniforms = {
|
|
[0] = { .name = "screen_size", .type = SG_UNIFORMTYPE_FLOAT4 },
|
|
},
|
|
},
|
|
};
|
|
} else if (response->failed) {
|
|
fprintf(stderr, "failed to load vertex shader\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void fragment_shader_loaded(const sfetch_response_t *response) {
|
|
if (response->fetched) {
|
|
state.scratch_shader_desc.fs = (sg_shader_stage_desc){
|
|
.source = response->data.ptr,
|
|
.entry = "fs_main",
|
|
.images[0].used = true,
|
|
.samplers[0].used = true,
|
|
.image_sampler_pairs[0] = { .glsl_name = "_group_0_binding_0_fs", .used = true, .image_slot = 0, .sampler_slot = 0 },
|
|
};
|
|
state.scratch_shader_desc.fs.source = response->data.ptr;
|
|
state.scratch_shader_desc.fs.entry = "fs_main";
|
|
} else if (response->failed) {
|
|
fprintf(stderr, "failed to load vertex shader\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void ed_init() {
|
|
uint8_t ttf_buffer[1<<20];
|
|
// TODO: grab default font from the system
|
|
FILE *ttf_file = fopen("./bin/JetBrainsMono-Medium.ttf", "rb");
|
|
if (!ttf_file) {
|
|
exit(1);
|
|
}
|
|
assert(fread(ttf_buffer, 1, 1<<20, ttf_file));
|
|
fclose(ttf_file);
|
|
|
|
stbtt_fontinfo font;
|
|
stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0));
|
|
|
|
sg_setup(&(sg_desc) {
|
|
.environment = sglue_environment(),
|
|
.logger.func = slog_func,
|
|
});
|
|
|
|
float vertices[] = {
|
|
// positions texture coords
|
|
-1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
|
|
1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
|
|
1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
|
|
-1.0f, -1.0f, 1.0f, 0.0f, 1.0f,
|
|
};
|
|
state.bind.vertex_buffers[0] = sg_make_buffer(&(sg_buffer_desc) {
|
|
.data = SG_RANGE(vertices)
|
|
});
|
|
|
|
const uint16_t indices[] = { 0, 1, 2, 0, 2, 3 };
|
|
state.bind.index_buffer = sg_make_buffer(&(sg_buffer_desc) {
|
|
.type = SG_BUFFERTYPE_INDEXBUFFER,
|
|
.data = SG_RANGE(indices)
|
|
});
|
|
|
|
sfetch_setup(&(sfetch_desc_t){ .logger.func = slog_func });
|
|
|
|
char vs_source[8000] = { 0 };
|
|
char fs_source[8000] = { 0 };
|
|
|
|
sfetch_handle_t vs_handle = sfetch_send(&(sfetch_request_t) {
|
|
#if defined (__APPLE__)
|
|
.path = "./bin/transpiled_shaders/vertex.metal",
|
|
#elif defined (__linux__) || defined (__unix__)
|
|
.path = "./shaders/vertex.vert",
|
|
#else
|
|
#error "Unsupported platform for shaders"
|
|
#endif
|
|
.callback = vertex_shader_loaded,
|
|
.buffer = { .ptr = vs_source, .size = sizeof(vs_source) },
|
|
});
|
|
sfetch_handle_t fs_handle = sfetch_send(&(sfetch_request_t) {
|
|
#if defined (__APPLE__)
|
|
.path = "./bin/transpiled_shaders/fragment.metal",
|
|
#elif defined (__linux__) || defined (__unix__)
|
|
.path = "./shaders/fragment.frag",
|
|
#else
|
|
#error "Unsupported platform for shaders"
|
|
#endif
|
|
.callback = fragment_shader_loaded,
|
|
.buffer = { .ptr = fs_source, .size = sizeof(fs_source) },
|
|
});
|
|
|
|
// block until files are loaded
|
|
while (sfetch_handle_valid(vs_handle) || sfetch_handle_valid(fs_handle)) {
|
|
sfetch_dowork();
|
|
}
|
|
|
|
const int font_bitmap_size = 1024;
|
|
const int rasterized_font_height = 64;
|
|
uint8_t *font_bitmap = context_alloc(font_bitmap_size*font_bitmap_size * sizeof(uint8_t));
|
|
|
|
state.gpu_ui_rects = newArray(GpuUiRect, 2000);
|
|
state.gpu_glyphs = newArray(GpuGlyph, 1024);
|
|
state.glyph_cache = newArray(GpuGlyph, 97);
|
|
|
|
int ascent, descent, line_gap;
|
|
float scale = stbtt_ScaleForPixelHeight(&font, rasterized_font_height);
|
|
stbtt_GetFontVMetrics(&font, &ascent, &descent, &line_gap);
|
|
|
|
for (size_t xx=0; xx < rasterized_font_height/2; ++xx) {
|
|
for (size_t yy=0; yy < rasterized_font_height; ++yy) {
|
|
font_bitmap[xx + yy * font_bitmap_size] = 0;
|
|
}
|
|
}
|
|
// manually add glyph for SPACE
|
|
pushArray(GpuGlyph, &state.glyph_cache, ((GpuGlyph){
|
|
.atlas_position = { 0 },
|
|
.size = { rasterized_font_height/4, rasterized_font_height },
|
|
.position = { 0 },
|
|
.y_offset = -rasterized_font_height,
|
|
}));
|
|
|
|
int x = rasterized_font_height/4;
|
|
int y = 0;
|
|
for (size_t i = 33; i < 33+96; ++i) {
|
|
int width,height, xoff,yoff;
|
|
uint8_t *bitmap = stbtt_GetCodepointBitmap(&font, scale, scale, (int)i, &width, &height, &xoff, &yoff);
|
|
|
|
if (x + width >= font_bitmap_size) {
|
|
x = 0;
|
|
y += (int)((float)(ascent - descent + line_gap)*scale);
|
|
}
|
|
|
|
size_t xxx = x;
|
|
for (size_t xx=0; xx < (size_t)width; ++xx) {
|
|
size_t yyy = y;
|
|
for (size_t yy=0; yy < (size_t)height; ++yy) {
|
|
font_bitmap[xxx + yyy * font_bitmap_size] = bitmap[xx + yy * width];
|
|
|
|
yyy += 1;
|
|
}
|
|
|
|
xxx += 1;
|
|
}
|
|
|
|
pushArray(GpuGlyph, &state.glyph_cache, ((GpuGlyph){
|
|
.atlas_position = { (float)(x), (float)(y) },
|
|
.size = { (float)width, (float)height },
|
|
.position = { (float)x, (float)y },
|
|
.y_offset = (float)(yoff),
|
|
}));
|
|
|
|
x += width;
|
|
}
|
|
|
|
state.bind.vertex_buffers[1] = sg_make_buffer(&(sg_buffer_desc) {
|
|
.size = state.gpu_glyphs.capacity * sizeof(GpuGlyph),
|
|
.usage = SG_USAGE_STREAM,
|
|
.label = "glyph buffer"
|
|
});
|
|
|
|
state.text_atlas_sampler = sg_make_sampler(&(sg_sampler_desc) { .mag_filter = SG_FILTER_LINEAR });
|
|
state.text_atlas_image = sg_make_image(&(sg_image_desc) {
|
|
.width = font_bitmap_size,
|
|
.height = font_bitmap_size,
|
|
.pixel_format = SG_PIXELFORMAT_R8,
|
|
.data.subimage[0][0] = { .ptr = font_bitmap, .size = font_bitmap_size*font_bitmap_size * sizeof(uint8_t) },
|
|
});
|
|
state.bind.fs.images[0] = state.text_atlas_image;
|
|
state.bind.fs.samplers[0] = state.text_atlas_sampler;
|
|
|
|
state.pass_action = (sg_pass_action) {
|
|
.colors[0] = {
|
|
.load_action = SG_LOADACTION_CLEAR,
|
|
.clear_value = { 0.0f, 0.0f, 0.0f, 1.0f }
|
|
}
|
|
};
|
|
sg_shader shd = sg_make_shader(&state.scratch_shader_desc);
|
|
state.pip = sg_make_pipeline(&(sg_pipeline_desc) {
|
|
.shader = shd,
|
|
.index_type = SG_INDEXTYPE_UINT16,
|
|
.layout = {
|
|
.buffers[1].step_func = SG_VERTEXSTEP_PER_INSTANCE,
|
|
.attrs = {
|
|
[0] = { .format=SG_VERTEXFORMAT_FLOAT3, .buffer_index = 0 },
|
|
[1] = { .format=SG_VERTEXFORMAT_FLOAT2, .buffer_index = 0 },
|
|
|
|
[2] = { .format=SG_VERTEXFORMAT_FLOAT2, .buffer_index = 1 },
|
|
[3] = { .format=SG_VERTEXFORMAT_FLOAT2, .buffer_index = 1 },
|
|
[4] = { .format=SG_VERTEXFORMAT_FLOAT2, .buffer_index = 1 },
|
|
[5] = { .format=SG_VERTEXFORMAT_FLOAT, .buffer_index = 1 },
|
|
},
|
|
},
|
|
});
|
|
|
|
queue_text(_String("But what even is text! []!@#$%^&*()_=+"), (float[]){ 0, 0 });
|
|
queue_text(_String("v0.1.0"), (float[]){ 32, 128 });
|
|
queue_text(_String("an_editor - what even"), (float[]){ 32, 256 });
|
|
|
|
state.ui_cx = init_ui_context();
|
|
|
|
string label = _String("Number 1");
|
|
ht_set(&state.ui_cx.cached_elements, label, &(ui_element_cache_data) {
|
|
.label = label,
|
|
.size = {
|
|
.axis = UI_AXIS_HORIZONTAL,
|
|
.computed_size = { 200, 256 },
|
|
}
|
|
});
|
|
|
|
for (size_t i = 0; i < state.ui_cx.cached_elements.capacity; ++i) {
|
|
if (state.ui_cx.cached_elements.key_slots[i].key.data != NULL) {
|
|
string text = state.ui_cx.cached_elements.key_slots[i].key;
|
|
|
|
ui_element_cache_data *value = ht_get(&state.ui_cx.cached_elements, text);
|
|
if (value) {
|
|
queue_text(text, (float[]){ (float)value->size.computed_size[0], (float)value->size.computed_size[1] });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void ed_frame() {
|
|
if (state.gpu_glyphs.size > 0) {
|
|
sg_update_buffer(state.bind.vertex_buffers[1], &(sg_range) {
|
|
.ptr = state.gpu_glyphs.data,
|
|
.size = state.gpu_glyphs.size * sizeof(GpuGlyph)
|
|
});
|
|
}
|
|
|
|
GpuUniformParams gpu_uniform_params = {
|
|
.screen_size = {
|
|
sapp_widthf(),
|
|
sapp_heightf(),
|
|
0,
|
|
0
|
|
},
|
|
};
|
|
|
|
sg_begin_pass(&(sg_pass) { .action = state.pass_action, .swapchain = sglue_swapchain() });
|
|
{
|
|
sg_apply_pipeline(state.pip);
|
|
sg_apply_bindings(&state.bind);
|
|
sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &(sg_range) { .ptr = &gpu_uniform_params, .size = sizeof(GpuUniformParams) });
|
|
sg_draw(0, 6, state.gpu_glyphs.size);
|
|
}
|
|
sg_end_pass();
|
|
sg_commit();
|
|
}
|
|
void ed_cleanup() {
|
|
sfetch_shutdown();
|
|
sg_shutdown();
|
|
}
|
|
void ed_event(const sapp_event *event) {
|
|
switch (event->type) {
|
|
case SAPP_EVENTTYPE_MOUSE_DOWN:
|
|
if (event->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
|
|
sapp_lock_mouse(true);
|
|
}
|
|
break;
|
|
|
|
case SAPP_EVENTTYPE_MOUSE_UP:
|
|
if (event->mouse_button == SAPP_MOUSEBUTTON_LEFT) {
|
|
sapp_lock_mouse(false);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
sapp_desc sokol_main(int argc, char *argv[]) {
|
|
return (sapp_desc) {
|
|
.width = 640,
|
|
.height = 480,
|
|
.init_cb = ed_init,
|
|
.frame_cb = ed_frame,
|
|
.cleanup_cb = ed_cleanup,
|
|
.event_cb = ed_event,
|
|
.icon.sokol_default = true,
|
|
.logger.func = slog_func,
|
|
};
|
|
}
|