Save
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
for %%a in (%*) do set "%%~a=1"
|
for %%a in (%*) do set "%%~a=1"
|
||||||
|
|
||||||
|
|
||||||
set common=-I ../src/ -g -fdiagnostics-absolute-paths -Wno-unsequenced -Wno-single-bit-bitfield-constant-conversion -Wall -Wno-missing-braces -Wextra -Wno-missing-field-initializers
|
set common=-I ../src/ -g -fdiagnostics-absolute-paths -Wno-unsequenced -Wno-single-bit-bitfield-constant-conversion -Wall -Wno-missing-braces -Wextra -Wno-missing-field-initializers
|
||||||
set wasm_flags=--target=wasm32 -nostdlib -mbulk-memory -msimd128 -Wl,-export-dynamic,--allow-undefined,--import-memory,--no-entry,--initial-memory=131072000,--max-memory=4294967296
|
set wasm_flags=--target=wasm32 -nostdlib -mbulk-memory -msimd128 -Wl,-export-dynamic,--allow-undefined,--import-memory,--no-entry,--initial-memory=131072000,--max-memory=4294967296
|
||||||
|
|
||||||
|
|||||||
@@ -1,470 +1,470 @@
|
|||||||
#include "core_platform.h"
|
#include "core_platform.h"
|
||||||
#include "core_ctx.h"
|
#include "core_ctx.h"
|
||||||
|
#include "stb_sprintf.h"
|
||||||
#if PLATFORM_WASM && PLATFORM_EMSCRIPTEN == 0
|
|
||||||
#define gb_wasm_export __attribute__((visibility("default")))
|
#if PLATFORM_WASM && PLATFORM_EMSCRIPTEN == 0
|
||||||
#define fn_wasm_export __attribute__((visibility("default")))
|
#define gb_wasm_export __attribute__((visibility("default")))
|
||||||
#define fn_wasm_import
|
#define fn_wasm_export __attribute__((visibility("default")))
|
||||||
|
#define fn_wasm_import
|
||||||
fn_wasm_import void wasm_alert(isize ptr, i32 len);
|
|
||||||
fn_wasm_import f64 wasm_parse_float(isize str, i32 len);
|
fn_wasm_import void wasm_alert(isize ptr, i32 len);
|
||||||
fn_wasm_import void wasm_write_to_console(isize str, i32 len);
|
fn_wasm_import f64 wasm_parse_float(isize str, i32 len);
|
||||||
|
fn_wasm_import void wasm_write_to_console(isize str, i32 len);
|
||||||
extern char __heap_base;
|
|
||||||
|
extern char __heap_base;
|
||||||
fn void *os_vmem_reserve(usize size) {
|
|
||||||
unused(size);
|
fn void *os_vmem_reserve(usize size) {
|
||||||
return NULL;
|
unused(size);
|
||||||
}
|
return NULL;
|
||||||
|
}
|
||||||
fn b32 os_vmem_commit(void *p, usize size) {
|
|
||||||
unused(p);
|
fn b32 os_vmem_commit(void *p, usize size) {
|
||||||
unused(size);
|
unused(p);
|
||||||
return false;
|
unused(size);
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
fn b32 os_vmem_release(void *p) {
|
|
||||||
unused(p);
|
fn b32 os_vmem_release(void *p) {
|
||||||
return true;
|
unused(p);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
fn b32 os_vmem_decommit(void *p, usize size) {
|
|
||||||
unused(p);
|
fn b32 os_vmem_decommit(void *p, usize size) {
|
||||||
unused(size);
|
unused(p);
|
||||||
return true;
|
unused(size);
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
fn void os_error_box(char *str) {
|
|
||||||
wasm_alert((isize)str, str_len(str));
|
fn void os_error_box(char *str) {
|
||||||
}
|
wasm_alert((isize)str, str_len(str));
|
||||||
|
}
|
||||||
fn void os_console_log(char *str) {
|
|
||||||
s8_t string = s8_from_char(str);
|
fn void os_console_log(char *str) {
|
||||||
if (s8_ends_with(string, s8("\n"))) {
|
s8_t string = s8_from_char(str);
|
||||||
string = s8_chop(string, 1);
|
if (s8_ends_with(string, s8("\n"))) {
|
||||||
}
|
string = s8_chop(string, 1);
|
||||||
wasm_write_to_console((isize)string.str, (i32)string.len);
|
}
|
||||||
}
|
wasm_write_to_console((isize)string.str, (i32)string.len);
|
||||||
|
}
|
||||||
fn f64 os_parse_float(char *str) {
|
|
||||||
return wasm_parse_float((isize)str, str_len((char *)str));
|
fn f64 os_parse_float(char *str) {
|
||||||
}
|
return wasm_parse_float((isize)str, str_len((char *)str));
|
||||||
|
}
|
||||||
fn void os_core_small_init(ma_arena_t *arena) {
|
|
||||||
tcx = ma_push_type(arena, thread_ctx_t);
|
fn void os_core_small_init(ma_arena_t *arena) {
|
||||||
tcx->log.break_on_error = true;
|
tcx = ma_push_type(arena, thread_ctx_t);
|
||||||
tcx->log.break_on_fatal = true;
|
tcx->log.break_on_error = true;
|
||||||
tcx->log.break_on_warning = true;
|
tcx->log.break_on_fatal = true;
|
||||||
tcx->log.print_file_and_line = true;
|
tcx->log.break_on_warning = true;
|
||||||
tcx->log.log_proc = default_log_proc;
|
tcx->log.print_file_and_line = true;
|
||||||
}
|
tcx->log.log_proc = default_log_proc;
|
||||||
|
}
|
||||||
fn void os_core_init(void) {
|
|
||||||
ma_arena_t perm = {0};
|
fn void os_core_init(void) {
|
||||||
isize page_size = kib(64);
|
ma_arena_t perm = {0};
|
||||||
isize page_count = __builtin_wasm_memory_size(0);
|
isize page_size = kib(64);
|
||||||
u8 *memory = (u8 *)&__heap_base;
|
isize page_count = __builtin_wasm_memory_size(0);
|
||||||
usize memory_size = page_count * (page_size) - (isize)memory;
|
u8 *memory = (u8 *)&__heap_base;
|
||||||
perm.data = memory;
|
usize memory_size = page_count * (page_size) - (isize)memory;
|
||||||
perm.commit = perm.reserve = memory_size;
|
perm.data = memory;
|
||||||
os_core_small_init(&perm);
|
perm.commit = perm.reserve = memory_size;
|
||||||
tcx->perm = perm;
|
os_core_small_init(&perm);
|
||||||
ma_push_arena_ex(&tcx->perm, &tcx->temp, mib(16));
|
tcx->perm = perm;
|
||||||
ma_push_arena_ex(&tcx->perm, &tcx->scratch[0], mib(2));
|
ma_push_arena_ex(&tcx->perm, &tcx->temp, mib(64));
|
||||||
ma_push_arena_ex(&tcx->perm, &tcx->scratch[1], kib(256));
|
ma_push_arena_ex(&tcx->perm, &tcx->scratch[0], mib(2));
|
||||||
ma_push_arena_ex(&tcx->perm, &tcx->scratch[2], kib(64));
|
ma_push_arena_ex(&tcx->perm, &tcx->scratch[1], kib(256));
|
||||||
debugf("memory size = %lld", memory_size);
|
ma_push_arena_ex(&tcx->perm, &tcx->scratch[2], kib(64));
|
||||||
}
|
debugf("memory size = %lld", memory_size);
|
||||||
#elif PLATFORM_WINDOWS
|
}
|
||||||
#pragma comment(lib, "user32.lib")
|
#elif PLATFORM_WINDOWS
|
||||||
#pragma comment(lib, "DbgHelp.lib")
|
#pragma comment(lib, "user32.lib")
|
||||||
#pragma comment(lib, "Shell32.lib")
|
#pragma comment(lib, "DbgHelp.lib")
|
||||||
#pragma comment(lib, "Ole32.lib")
|
#pragma comment(lib, "Shell32.lib")
|
||||||
#define NOMINMAX
|
#pragma comment(lib, "Ole32.lib")
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define NOMINMAX
|
||||||
#include <windows.h>
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <DbgHelp.h>
|
#include <windows.h>
|
||||||
#include <TlHelp32.h>
|
#include <DbgHelp.h>
|
||||||
#include <Shlobj.h>
|
#include <TlHelp32.h>
|
||||||
#include <math.h>
|
#include <Shlobj.h>
|
||||||
#include <string.h>
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdio.h>
|
||||||
#include <signal.h>
|
#include <stdlib.h>
|
||||||
|
#include <signal.h>
|
||||||
typedef struct b_stacktrace_tag* b_stacktrace_handle;
|
|
||||||
|
typedef struct b_stacktrace_tag* b_stacktrace_handle;
|
||||||
/*
|
|
||||||
Returns a stack-trace handle from the point of view of the caller which
|
/*
|
||||||
can be expanded to a string via b_stacktrace_to_string.
|
Returns a stack-trace handle from the point of view of the caller which
|
||||||
The handle is allocated with `malloc` and needs to be freed with `free`
|
can be expanded to a string via b_stacktrace_to_string.
|
||||||
*/
|
The handle is allocated with `malloc` and needs to be freed with `free`
|
||||||
fn b_stacktrace_handle b_stacktrace_get();
|
*/
|
||||||
|
fn b_stacktrace_handle b_stacktrace_get();
|
||||||
/*
|
|
||||||
Converts a stack-trace handle to a human-readable string.
|
/*
|
||||||
The string is allocated with `malloc` and needs to be freed with `free`
|
Converts a stack-trace handle to a human-readable string.
|
||||||
*/
|
The string is allocated with `malloc` and needs to be freed with `free`
|
||||||
fn char* b_stacktrace_to_string(b_stacktrace_handle stacktrace);
|
*/
|
||||||
|
fn char* b_stacktrace_to_string(b_stacktrace_handle stacktrace);
|
||||||
/*
|
|
||||||
Returns a human-readable stack-trace string from the point of view of the
|
/*
|
||||||
caller.
|
Returns a human-readable stack-trace string from the point of view of the
|
||||||
The string is allocated with `malloc` and needs to be freed with `free`
|
caller.
|
||||||
*/
|
The string is allocated with `malloc` and needs to be freed with `free`
|
||||||
fn char* b_stacktrace_get_string(void);
|
*/
|
||||||
|
fn char* b_stacktrace_get_string(void);
|
||||||
/* version */
|
|
||||||
|
/* version */
|
||||||
typedef struct print_buf {
|
|
||||||
char* buf;
|
typedef struct print_buf {
|
||||||
int pos;
|
char* buf;
|
||||||
int size;
|
int pos;
|
||||||
} print_buf;
|
int size;
|
||||||
|
} print_buf;
|
||||||
static print_buf buf_init(void) {
|
|
||||||
print_buf ret = {
|
static print_buf buf_init(void) {
|
||||||
(char*) malloc(1024),
|
print_buf ret = {
|
||||||
0,
|
(char*) malloc(1024),
|
||||||
1024
|
0,
|
||||||
};
|
1024
|
||||||
return ret;
|
};
|
||||||
}
|
return ret;
|
||||||
|
}
|
||||||
static void buf_printf(print_buf* b, const char* fmt, ...) {
|
|
||||||
va_list args;
|
static void buf_printf(print_buf* b, const char* fmt, ...) {
|
||||||
va_start(args, fmt);
|
va_list args;
|
||||||
const int len = vsnprintf(NULL, 0, fmt, args) + 1;
|
va_start(args, fmt);
|
||||||
va_end(args);
|
const int len = stbsp_vsnprintf(NULL, 0, fmt, args) + 1;
|
||||||
|
va_end(args);
|
||||||
const int new_end = b->pos + len;
|
|
||||||
|
const int new_end = b->pos + len;
|
||||||
if (new_end > b->size) {
|
|
||||||
while (new_end > b->size) b->size *= 2;
|
if (new_end > b->size) {
|
||||||
b->buf = (char*)realloc(b->buf, b->size);
|
while (new_end > b->size) b->size *= 2;
|
||||||
}
|
b->buf = (char*)realloc(b->buf, b->size);
|
||||||
|
}
|
||||||
va_start(args, fmt);
|
|
||||||
b->pos += vsnprintf(b->buf + b->pos, len, fmt, args);
|
va_start(args, fmt);
|
||||||
va_end(args);
|
b->pos += stbsp_vsnprintf(b->buf + b->pos, len, fmt, args);
|
||||||
}
|
va_end(args);
|
||||||
|
}
|
||||||
char* b_stacktrace_get_string(void) {
|
|
||||||
b_stacktrace_handle h = b_stacktrace_get();
|
char* b_stacktrace_get_string(void) {
|
||||||
char* ret = b_stacktrace_to_string(h);
|
b_stacktrace_handle h = b_stacktrace_get();
|
||||||
free(h);
|
char* ret = b_stacktrace_to_string(h);
|
||||||
return ret;
|
free(h);
|
||||||
}
|
return ret;
|
||||||
|
}
|
||||||
#define B_STACKTRACE_MAX_DEPTH 1024
|
|
||||||
|
#define B_STACKTRACE_MAX_DEPTH 1024
|
||||||
#define B_STACKTRACE_ERROR_FLAG ((DWORD64)1 << 63)
|
#define B_STACKTRACE_ERROR_FLAG ((DWORD64)1 << 63)
|
||||||
|
|
||||||
typedef struct b_stacktrace_entry {
|
typedef struct b_stacktrace_entry {
|
||||||
DWORD64 AddrPC_Offset;
|
DWORD64 AddrPC_Offset;
|
||||||
DWORD64 AddrReturn_Offset;
|
DWORD64 AddrReturn_Offset;
|
||||||
} b_stacktrace_entry;
|
} b_stacktrace_entry;
|
||||||
|
|
||||||
static int SymInitialize_called = 0;
|
static int SymInitialize_called = 0;
|
||||||
|
|
||||||
b_stacktrace_handle b_stacktrace_get(void) {
|
b_stacktrace_handle b_stacktrace_get(void) {
|
||||||
HANDLE process = GetCurrentProcess();
|
HANDLE process = GetCurrentProcess();
|
||||||
HANDLE thread = GetCurrentThread();
|
HANDLE thread = GetCurrentThread();
|
||||||
CONTEXT context;
|
CONTEXT context;
|
||||||
STACKFRAME64 frame; /* in/out stackframe */
|
STACKFRAME64 frame; /* in/out stackframe */
|
||||||
DWORD imageType;
|
DWORD imageType;
|
||||||
b_stacktrace_entry* ret = (b_stacktrace_entry*)malloc(B_STACKTRACE_MAX_DEPTH * sizeof(b_stacktrace_entry));
|
b_stacktrace_entry* ret = (b_stacktrace_entry*)malloc(B_STACKTRACE_MAX_DEPTH * sizeof(b_stacktrace_entry));
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
if (!SymInitialize_called) {
|
if (!SymInitialize_called) {
|
||||||
SymInitialize(process, NULL, TRUE);
|
SymInitialize(process, NULL, TRUE);
|
||||||
SymInitialize_called = 1;
|
SymInitialize_called = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
RtlCaptureContext(&context);
|
RtlCaptureContext(&context);
|
||||||
|
|
||||||
memset(&frame, 0, sizeof(frame));
|
memset(&frame, 0, sizeof(frame));
|
||||||
#ifdef _M_IX86
|
#ifdef _M_IX86
|
||||||
imageType = IMAGE_FILE_MACHINE_I386;
|
imageType = IMAGE_FILE_MACHINE_I386;
|
||||||
frame.AddrPC.Offset = context.Eip;
|
frame.AddrPC.Offset = context.Eip;
|
||||||
frame.AddrPC.Mode = AddrModeFlat;
|
frame.AddrPC.Mode = AddrModeFlat;
|
||||||
frame.AddrFrame.Offset = context.Ebp;
|
frame.AddrFrame.Offset = context.Ebp;
|
||||||
frame.AddrFrame.Mode = AddrModeFlat;
|
frame.AddrFrame.Mode = AddrModeFlat;
|
||||||
frame.AddrStack.Offset = context.Esp;
|
frame.AddrStack.Offset = context.Esp;
|
||||||
frame.AddrStack.Mode = AddrModeFlat;
|
frame.AddrStack.Mode = AddrModeFlat;
|
||||||
#elif _M_X64
|
#elif _M_X64
|
||||||
imageType = IMAGE_FILE_MACHINE_AMD64;
|
imageType = IMAGE_FILE_MACHINE_AMD64;
|
||||||
frame.AddrPC.Offset = context.Rip;
|
frame.AddrPC.Offset = context.Rip;
|
||||||
frame.AddrPC.Mode = AddrModeFlat;
|
frame.AddrPC.Mode = AddrModeFlat;
|
||||||
frame.AddrFrame.Offset = context.Rsp;
|
frame.AddrFrame.Offset = context.Rsp;
|
||||||
frame.AddrFrame.Mode = AddrModeFlat;
|
frame.AddrFrame.Mode = AddrModeFlat;
|
||||||
frame.AddrStack.Offset = context.Rsp;
|
frame.AddrStack.Offset = context.Rsp;
|
||||||
frame.AddrStack.Mode = AddrModeFlat;
|
frame.AddrStack.Mode = AddrModeFlat;
|
||||||
#elif _M_IA64
|
#elif _M_IA64
|
||||||
imageType = IMAGE_FILE_MACHINE_IA64;
|
imageType = IMAGE_FILE_MACHINE_IA64;
|
||||||
frame.AddrPC.Offset = context.StIIP;
|
frame.AddrPC.Offset = context.StIIP;
|
||||||
frame.AddrPC.Mode = AddrModeFlat;
|
frame.AddrPC.Mode = AddrModeFlat;
|
||||||
frame.AddrFrame.Offset = context.IntSp;
|
frame.AddrFrame.Offset = context.IntSp;
|
||||||
frame.AddrFrame.Mode = AddrModeFlat;
|
frame.AddrFrame.Mode = AddrModeFlat;
|
||||||
frame.AddrBStore.Offset = context.RsBSP;
|
frame.AddrBStore.Offset = context.RsBSP;
|
||||||
frame.AddrBStore.Mode = AddrModeFlat;
|
frame.AddrBStore.Mode = AddrModeFlat;
|
||||||
frame.AddrStack.Offset = context.IntSp;
|
frame.AddrStack.Offset = context.IntSp;
|
||||||
frame.AddrStack.Mode = AddrModeFlat;
|
frame.AddrStack.Mode = AddrModeFlat;
|
||||||
#else
|
#else
|
||||||
#error "Platform not supported!"
|
#error "Platform not supported!"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
b_stacktrace_entry* cur = ret + i++;
|
b_stacktrace_entry* cur = ret + i++;
|
||||||
if (i == B_STACKTRACE_MAX_DEPTH) {
|
if (i == B_STACKTRACE_MAX_DEPTH) {
|
||||||
cur->AddrPC_Offset = 0;
|
cur->AddrPC_Offset = 0;
|
||||||
cur->AddrReturn_Offset = 0;
|
cur->AddrReturn_Offset = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!StackWalk64(imageType, process, thread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
|
if (!StackWalk64(imageType, process, thread, &frame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
|
||||||
cur->AddrPC_Offset = frame.AddrPC.Offset;
|
cur->AddrPC_Offset = frame.AddrPC.Offset;
|
||||||
cur->AddrReturn_Offset = B_STACKTRACE_ERROR_FLAG; /* mark error */
|
cur->AddrReturn_Offset = B_STACKTRACE_ERROR_FLAG; /* mark error */
|
||||||
cur->AddrReturn_Offset |= GetLastError();
|
cur->AddrReturn_Offset |= GetLastError();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
cur->AddrPC_Offset = frame.AddrPC.Offset;
|
cur->AddrPC_Offset = frame.AddrPC.Offset;
|
||||||
cur->AddrReturn_Offset = frame.AddrReturn.Offset;
|
cur->AddrReturn_Offset = frame.AddrReturn.Offset;
|
||||||
|
|
||||||
if (frame.AddrReturn.Offset == 0) {
|
if (frame.AddrReturn.Offset == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (b_stacktrace_handle)(ret);
|
return (b_stacktrace_handle)(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
char* b_stacktrace_to_string(b_stacktrace_handle h) {
|
char* b_stacktrace_to_string(b_stacktrace_handle h) {
|
||||||
const b_stacktrace_entry* entries = (b_stacktrace_entry*)h;
|
const b_stacktrace_entry* entries = (b_stacktrace_entry*)h;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
HANDLE process = GetCurrentProcess();
|
HANDLE process = GetCurrentProcess();
|
||||||
print_buf out = buf_init();
|
print_buf out = buf_init();
|
||||||
IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + 1024);
|
IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + 1024);
|
||||||
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
|
||||||
symbol->MaxNameLength = 1024;
|
symbol->MaxNameLength = 1024;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
IMAGEHLP_LINE64 lineData;
|
IMAGEHLP_LINE64 lineData;
|
||||||
DWORD lineOffset = 0;
|
DWORD lineOffset = 0;
|
||||||
DWORD64 symOffset = 0;
|
DWORD64 symOffset = 0;
|
||||||
const b_stacktrace_entry* cur = entries + i++;
|
const b_stacktrace_entry* cur = entries + i++;
|
||||||
|
|
||||||
if (cur->AddrReturn_Offset & B_STACKTRACE_ERROR_FLAG) {
|
if (cur->AddrReturn_Offset & B_STACKTRACE_ERROR_FLAG) {
|
||||||
DWORD error = cur->AddrReturn_Offset & 0xFFFFFFFF;
|
DWORD error = cur->AddrReturn_Offset & 0xFFFFFFFF;
|
||||||
buf_printf(&out, "StackWalk64 error: %d @ %p\n", error, cur->AddrPC_Offset);
|
buf_printf(&out, "StackWalk64 error: %d @ %p\n", error, cur->AddrPC_Offset);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cur->AddrPC_Offset == cur->AddrReturn_Offset) {
|
if (cur->AddrPC_Offset == cur->AddrReturn_Offset) {
|
||||||
buf_printf(&out, "Stack overflow @ %p\n", cur->AddrPC_Offset);
|
buf_printf(&out, "Stack overflow @ %p\n", cur->AddrPC_Offset);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
SymGetLineFromAddr64(process, cur->AddrPC_Offset, &lineOffset, &lineData);
|
SymGetLineFromAddr64(process, cur->AddrPC_Offset, &lineOffset, &lineData);
|
||||||
buf_printf(&out, "%s(%d): ", lineData.FileName, lineData.LineNumber);
|
buf_printf(&out, "%s(%d): ", lineData.FileName, lineData.LineNumber);
|
||||||
|
|
||||||
if (SymGetSymFromAddr64(process, cur->AddrPC_Offset, &symOffset, symbol)) {
|
if (SymGetSymFromAddr64(process, cur->AddrPC_Offset, &symOffset, symbol)) {
|
||||||
buf_printf(&out, "%s\n", symbol->Name);
|
buf_printf(&out, "%s\n", symbol->Name);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
buf_printf(&out, " Unkown symbol @ %p\n", cur->AddrPC_Offset);
|
buf_printf(&out, " Unkown symbol @ %p\n", cur->AddrPC_Offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cur->AddrReturn_Offset == 0) {
|
if (cur->AddrReturn_Offset == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(symbol);
|
free(symbol);
|
||||||
return out.buf;
|
return out.buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void *os_vmem_reserve(usize size) {
|
fn void *os_vmem_reserve(usize size) {
|
||||||
void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
|
void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn b32 os_vmem_commit(void *p, usize size) {
|
fn b32 os_vmem_commit(void *p, usize size) {
|
||||||
void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
return result ? true : false;
|
return result ? true : false;
|
||||||
}
|
}
|
||||||
fn b32 os_vmem_release(void *p) {
|
fn b32 os_vmem_release(void *p) {
|
||||||
BOOL result = VirtualFree(p, 0, MEM_RELEASE);
|
BOOL result = VirtualFree(p, 0, MEM_RELEASE);
|
||||||
return result ? true : false;
|
return result ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn b32 os_vmem_decommit(void *p, usize size) {
|
fn b32 os_vmem_decommit(void *p, usize size) {
|
||||||
BOOL result = VirtualFree(p, size, MEM_DECOMMIT);
|
BOOL result = VirtualFree(p, size, MEM_DECOMMIT);
|
||||||
return result ? true : false;
|
return result ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_error_box(char *str) {
|
fn void os_error_box(char *str) {
|
||||||
OutputDebugStringA(str);
|
OutputDebugStringA(str);
|
||||||
fprintf(stderr, "%s", str);
|
fprintf(stderr, "%s", str);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
MessageBoxA(NULL, str, "fatal error", MB_OK);
|
MessageBoxA(NULL, str, "fatal error", MB_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_console_log(char *str) {
|
fn void os_console_log(char *str) {
|
||||||
OutputDebugStringA(str);
|
OutputDebugStringA(str);
|
||||||
fputs(str, stdout);
|
fputs(str, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn f64 os_parse_float(char *str) {
|
fn f64 os_parse_float(char *str) {
|
||||||
return strtod(str, NULL);
|
return strtod(str, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_win32_crash_handler(int signal) {
|
fn void os_win32_crash_handler(int signal) {
|
||||||
printf("signal: %d\n", signal);
|
printf("signal: %d\n", signal);
|
||||||
puts(b_stacktrace_get_string());
|
puts(b_stacktrace_get_string());
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_win32_register_crash_handler() {
|
fn void os_win32_register_crash_handler() {
|
||||||
signal(SIGSEGV, os_win32_crash_handler);
|
signal(SIGSEGV, os_win32_crash_handler);
|
||||||
signal(SIGABRT, os_win32_crash_handler);
|
signal(SIGABRT, os_win32_crash_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_core_small_init(ma_arena_t *arena) {
|
fn void os_core_small_init(ma_arena_t *arena) {
|
||||||
tcx = ma_push_type(arena, thread_ctx_t);
|
tcx = ma_push_type(arena, thread_ctx_t);
|
||||||
tcx->log.break_on_error = true;
|
tcx->log.break_on_error = true;
|
||||||
tcx->log.break_on_fatal = true;
|
tcx->log.break_on_fatal = true;
|
||||||
tcx->log.break_on_warning = true;
|
tcx->log.break_on_warning = true;
|
||||||
tcx->log.print_file_and_line = true;
|
tcx->log.print_file_and_line = true;
|
||||||
tcx->log.log_proc = default_log_proc;
|
tcx->log.log_proc = default_log_proc;
|
||||||
os_win32_register_crash_handler();
|
os_win32_register_crash_handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_core_init(void) {
|
fn void os_core_init(void) {
|
||||||
ma_arena_t perm = {0};
|
ma_arena_t perm = {0};
|
||||||
os_core_small_init(&perm);
|
os_core_small_init(&perm);
|
||||||
tcx->perm = perm;
|
tcx->perm = perm;
|
||||||
}
|
}
|
||||||
#elif PLATFORM_POSIX
|
#elif PLATFORM_POSIX
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
#if !PLATFORM_EMSCRIPTEN
|
#if !PLATFORM_EMSCRIPTEN
|
||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
#include <backtrace.h>
|
#include <backtrace.h>
|
||||||
#endif
|
#endif
|
||||||
fn void *os_vmem_reserve(usize size) {
|
fn void *os_vmem_reserve(usize size) {
|
||||||
void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
||||||
return result == MAP_FAILED ? NULL : result;
|
return result == MAP_FAILED ? NULL : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn b32 os_vmem_commit(void *ptr, usize size) {
|
fn b32 os_vmem_commit(void *ptr, usize size) {
|
||||||
mprotect(ptr, size, PROT_READ|PROT_WRITE);
|
mprotect(ptr, size, PROT_READ|PROT_WRITE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn b32 os_vmem_release(void *ptr) {
|
fn b32 os_vmem_release(void *ptr) {
|
||||||
int result = munmap(ptr, 0);
|
int result = munmap(ptr, 0);
|
||||||
return result == 0 ? true : false;
|
return result == 0 ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn b32 os_vmem_decommit(void *ptr, usize size) {
|
fn b32 os_vmem_decommit(void *ptr, usize size) {
|
||||||
madvise(ptr, size, MADV_DONTNEED);
|
madvise(ptr, size, MADV_DONTNEED);
|
||||||
mprotect(ptr, size, PROT_NONE);
|
mprotect(ptr, size, PROT_NONE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_error_box(char *str) {
|
fn void os_error_box(char *str) {
|
||||||
fprintf(stderr, "%s", str);
|
fprintf(stderr, "%s", str);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_console_log(char *str) {
|
fn void os_console_log(char *str) {
|
||||||
fputs(str, stdout);
|
fputs(str, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn f64 os_parse_float(char *str) {
|
fn f64 os_parse_float(char *str) {
|
||||||
return strtod(str, NULL);
|
return strtod(str, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
gb struct backtrace_state *backtrace_state = NULL;
|
gb struct backtrace_state *backtrace_state = NULL;
|
||||||
|
|
||||||
fn void os_core_backtrace_error_callback(void *data, const char *msg, int errnum) {
|
fn void os_core_backtrace_error_callback(void *data, const char *msg, int errnum) {
|
||||||
unused(data);
|
unused(data);
|
||||||
errorf("libbacktrace error: %s (errnum: %d)\n", msg, errnum);
|
errorf("libbacktrace error: %s (errnum: %d)\n", msg, errnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn int os_core_backtrace_print_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) {
|
fn int os_core_backtrace_print_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) {
|
||||||
unused(data);
|
unused(data);
|
||||||
unused(pc);
|
unused(pc);
|
||||||
b32 printed = false;
|
b32 printed = false;
|
||||||
if (filename != NULL) {
|
if (filename != NULL) {
|
||||||
char buffer[512];
|
char buffer[512];
|
||||||
char *f = realpath(filename, buffer);
|
char *f = realpath(filename, buffer);
|
||||||
printf("%s:%d:1: ", f, lineno);
|
printf("%s:%d:1: ", f, lineno);
|
||||||
printed = true;
|
printed = true;
|
||||||
}
|
}
|
||||||
if (function != NULL) {
|
if (function != NULL) {
|
||||||
printf("%s", function);
|
printf("%s", function);
|
||||||
printed = true;
|
printed = true;
|
||||||
}
|
}
|
||||||
if (printed) {
|
if (printed) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !PLATFORM_EMSCRIPTEN
|
#if !PLATFORM_EMSCRIPTEN
|
||||||
fn void os_unix_crash_handler(int signal, siginfo_t* info, void* context) {
|
fn void os_unix_crash_handler(int signal, siginfo_t* info, void* context) {
|
||||||
unused(context);
|
unused(context);
|
||||||
unused(signal);
|
unused(signal);
|
||||||
unused(info);
|
unused(info);
|
||||||
backtrace_full(backtrace_state, 2, os_core_backtrace_print_callback, os_core_backtrace_error_callback, NULL);
|
backtrace_full(backtrace_state, 2, os_core_backtrace_print_callback, os_core_backtrace_error_callback, NULL);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_unix_register_crash_handler() {
|
fn void os_unix_register_crash_handler() {
|
||||||
backtrace_state = backtrace_create_state(NULL, 1, os_core_backtrace_error_callback, NULL);
|
backtrace_state = backtrace_create_state(NULL, 1, os_core_backtrace_error_callback, NULL);
|
||||||
|
|
||||||
struct sigaction sa;
|
struct sigaction sa;
|
||||||
sa.sa_sigaction = os_unix_crash_handler;
|
sa.sa_sigaction = os_unix_crash_handler;
|
||||||
sigemptyset(&sa.sa_mask);
|
sigemptyset(&sa.sa_mask);
|
||||||
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||||
|
|
||||||
sigaction(SIGSEGV, &sa, NULL);
|
sigaction(SIGSEGV, &sa, NULL);
|
||||||
sigaction(SIGABRT, &sa, NULL);
|
sigaction(SIGABRT, &sa, NULL);
|
||||||
sigaction(SIGBUS, &sa, NULL);
|
sigaction(SIGBUS, &sa, NULL);
|
||||||
sigaction(SIGILL, &sa, NULL);
|
sigaction(SIGILL, &sa, NULL);
|
||||||
sigaction(SIGFPE, &sa, NULL);
|
sigaction(SIGFPE, &sa, NULL);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
fn void os_core_small_init(ma_arena_t *arena) {
|
fn void os_core_small_init(ma_arena_t *arena) {
|
||||||
tcx = ma_push_type(arena, thread_ctx_t);
|
tcx = ma_push_type(arena, thread_ctx_t);
|
||||||
tcx->log.break_on_error = true;
|
tcx->log.break_on_error = true;
|
||||||
tcx->log.break_on_fatal = true;
|
tcx->log.break_on_fatal = true;
|
||||||
tcx->log.break_on_warning = true;
|
tcx->log.break_on_warning = true;
|
||||||
tcx->log.print_file_and_line = true;
|
tcx->log.print_file_and_line = true;
|
||||||
tcx->log.log_proc = default_log_proc;
|
tcx->log.log_proc = default_log_proc;
|
||||||
#if !PLATFORM_EMSCRIPTEN
|
#if !PLATFORM_EMSCRIPTEN
|
||||||
os_unix_register_crash_handler();
|
os_unix_register_crash_handler();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void os_core_init(void) {
|
fn void os_core_init(void) {
|
||||||
ma_arena_t perm = {0};
|
ma_arena_t perm = {0};
|
||||||
os_core_small_init(&perm);
|
os_core_small_init(&perm);
|
||||||
tcx->perm = perm;
|
tcx->perm = perm;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,57 +4,138 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Canvas</title>
|
<title>Canvas</title>
|
||||||
<style>
|
<style>
|
||||||
html,body{margin:0;height:100%;overflow:hidden;padding:0;font-size: 100px;font-family: FiraCode;}
|
html,body{margin:0;height:100%;overflow:hidden;padding:0;}
|
||||||
canvas{display:block;width:100%;height:250px;border-bottom: dotted;}
|
canvas{display:block;width:100%;height:100%;}
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'FiraCode';
|
|
||||||
src: url('./FiraCode-Regular.ttf');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
div{
|
|
||||||
position:relative;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="glcanvas"></canvas>
|
<canvas id="glcanvas"></canvas>
|
||||||
<div>
|
|
||||||
<p>Hello world (html)</p>
|
|
||||||
</div>
|
|
||||||
<canvas id="2dcanvas"></canvas>
|
|
||||||
<script>
|
<script>
|
||||||
function createShader(gl, type, src) {
|
|
||||||
const sh = gl.createShader(type);
|
|
||||||
gl.shaderSource(sh, src);
|
|
||||||
gl.compileShader(sh);
|
|
||||||
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
|
|
||||||
console.error('Shader compile error:', gl.getShaderInfoLog(sh));
|
|
||||||
gl.deleteShader(sh);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return sh;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createProgram(gl, vs, fs) {
|
CANVAS = null;
|
||||||
const prg = gl.createProgram();
|
GL = null;
|
||||||
gl.attachShader(prg, vs);
|
class wglRender {
|
||||||
gl.attachShader(prg, fs);
|
constructor() {
|
||||||
gl.linkProgram(prg);
|
const VERTEX_SRC = `#version 300 es
|
||||||
if (!gl.getProgramParameter(prg, gl.LINK_STATUS)) {
|
precision mediump float;
|
||||||
console.error('Program link error:', gl.getProgramInfoLog(prg));
|
in vec2 a_position;
|
||||||
gl.deleteProgram(prg);
|
out vec2 v_texCoord;
|
||||||
return null;
|
void main() {
|
||||||
|
v_texCoord = (a_position + 1.0) * 0.5; // [0,1]
|
||||||
|
v_texCoord.y = (1.0 - v_texCoord.y); // Flip the Y to get the standard memory behavior
|
||||||
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const FRAGMENT_SRC = `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
out vec4 outColor;
|
||||||
|
void main() {
|
||||||
|
outColor = texture(u_texture, v_texCoord);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
CANVAS = document.getElementById('glcanvas');
|
||||||
|
GL = CANVAS.getContext('webgl2');
|
||||||
|
if (!GL) { alert('WebGL 2 not supported'); return; }
|
||||||
|
|
||||||
|
const vs = this.createShader(GL.VERTEX_SHADER, VERTEX_SRC);
|
||||||
|
const fs = this.createShader(GL.FRAGMENT_SHADER, FRAGMENT_SRC);
|
||||||
|
this.program = this.createProgram(vs, fs);
|
||||||
|
|
||||||
|
this.posLoc = GL.getAttribLocation(this.program, 'a_position');
|
||||||
|
this.texLoc = GL.getUniformLocation(this.program, 'u_texture');
|
||||||
|
|
||||||
|
// Fullscreen quad
|
||||||
|
const quad = new Float32Array([
|
||||||
|
-1, -1, // bottom left
|
||||||
|
1, -1, // bottom right
|
||||||
|
-1, 1, // top left
|
||||||
|
-1, 1, // top left
|
||||||
|
1, -1, // bottom right
|
||||||
|
1, 1 // top right
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.vao = GL.createVertexArray();
|
||||||
|
GL.bindVertexArray(this.vao);
|
||||||
|
this.vbo = GL.createBuffer();
|
||||||
|
GL.bindBuffer(GL.ARRAY_BUFFER, this.vbo);
|
||||||
|
GL.bufferData(GL.ARRAY_BUFFER, quad, GL.STATIC_DRAW);
|
||||||
|
GL.enableVertexAttribArray(this.posLoc);
|
||||||
|
GL.vertexAttribPointer(this.posLoc, 2, GL.FLOAT, false, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
GL.useProgram(this.program);
|
||||||
|
GL.bindVertexArray(this.vao);
|
||||||
|
GL.activeTexture(GL.TEXTURE0);
|
||||||
|
GL.bindTexture(GL.TEXTURE_2D, this.texture);
|
||||||
|
GL.uniform1i(this.texLoc, 0);
|
||||||
|
GL.drawArrays(GL.TRIANGLES, 0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTextureSize(wasm) {
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
const w = Math.floor(CANVAS.clientWidth * dpr);
|
||||||
|
const h = Math.floor(CANVAS.clientHeight * dpr);
|
||||||
|
if (w !== this.w || h !== this.h) {
|
||||||
|
CANVAS.width = w;
|
||||||
|
CANVAS.height = h;
|
||||||
|
GL.viewport(0, 0, w, h);
|
||||||
|
|
||||||
|
this.w = w;
|
||||||
|
this.h = h;
|
||||||
|
if (this.texture) {
|
||||||
|
GL.deleteTexture(this.texture);
|
||||||
|
}
|
||||||
|
const rawPtr = wasm.exports.update(w, h, window.devicePixelRatio || 1.0);
|
||||||
|
const pixels = wasm.getU8View(rawPtr, w*h*4);
|
||||||
|
{
|
||||||
|
this.texture = GL.createTexture();
|
||||||
|
GL.bindTexture(GL.TEXTURE_2D, this.texture);
|
||||||
|
// Allocate an empty texture of the canvas size
|
||||||
|
GL.texImage2D(
|
||||||
|
GL.TEXTURE_2D, 0, GL.RGBA8,
|
||||||
|
w, h, 0,
|
||||||
|
GL.RGBA, GL.UNSIGNED_BYTE, pixels
|
||||||
|
);
|
||||||
|
// Set parameters – no filtering needed for empty data
|
||||||
|
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
|
||||||
|
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
|
||||||
|
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
|
||||||
|
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GL.activeTexture(GL.TEXTURE0);
|
||||||
|
// GL.bindTexture(GL.TEXTURE_2D, this.texture);
|
||||||
|
// GL.texSubImage2D(GL.TEXTURE_2D, 0, 0, 0, w, h, GL.RGBA, GL.UNSIGNED_BYTE,pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createShader(type, src) {
|
||||||
|
const sh = GL.createShader(type);
|
||||||
|
GL.shaderSource(sh, src);
|
||||||
|
GL.compileShader(sh);
|
||||||
|
if (!GL.getShaderParameter(sh, GL.COMPILE_STATUS)) {
|
||||||
|
console.error('Shader compile error:', GL.getShaderInfoLog(sh));
|
||||||
|
GL.deleteShader(sh);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sh;
|
||||||
|
}
|
||||||
|
|
||||||
|
createProgram(vs, fs) {
|
||||||
|
const prg = GL.createProgram();
|
||||||
|
GL.attachShader(prg, vs);
|
||||||
|
GL.attachShader(prg, fs);
|
||||||
|
GL.linkProgram(prg);
|
||||||
|
if (!GL.getProgramParameter(prg, GL.LINK_STATUS)) {
|
||||||
|
console.error('Program link error:', GL.getProgramInfoLog(prg));
|
||||||
|
GL.deleteProgram(prg);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return prg;
|
||||||
}
|
}
|
||||||
return prg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WASMMemory {
|
class WASMMemory {
|
||||||
@@ -67,18 +148,23 @@ class WASMMemory {
|
|||||||
this.exports = null;
|
this.exports = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
readString(str, len) {
|
decodeString(str, len) {
|
||||||
const arr = this.u8.subarray(str, str+len);
|
const arr = this.u8.subarray(str, str+len);
|
||||||
const text = this.utf8decoder.decode(arr);
|
const text = this.utf8decoder.decode(arr);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
readUint8(p, len) {
|
getU8View(p, len) {
|
||||||
const arr = this.u8.subarray(p, p+len);
|
const arr = this.u8.subarray(p, p+len);
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeString(ptr, ptr_len, string) {
|
getView(p, len) {
|
||||||
|
const res = new DataView(this.mem.buffer, p, p+len);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeString(ptr, ptr_len, string) {
|
||||||
const bytes = this.utf8encoder.encode(string);
|
const bytes = this.utf8encoder.encode(string);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (; i < bytes.length && i < (ptr_len-1); i += 1) {
|
for (; i < bytes.length && i < (ptr_len-1); i += 1) {
|
||||||
@@ -94,10 +180,10 @@ async function WASMInit(name) {
|
|||||||
const binary = await request.arrayBuffer();
|
const binary = await request.arrayBuffer();
|
||||||
const import_table = {
|
const import_table = {
|
||||||
memory: mem.mem,
|
memory: mem.mem,
|
||||||
wasm_parse_float: (str, len) => { return parseFloat(mem.readString(str, len)); },
|
wasm_parse_float: (str, len) => { return parseFloat(mem.decodeString(str, len)); },
|
||||||
wasm_alert: (str, len) => { alert(mem.readString(str,len)); },
|
wasm_alert: (str, len) => { alert(mem.decodeString(str,len)); },
|
||||||
wasm_trap: () => { throw new Error(); },
|
wasm_trap: () => { throw new Error(); },
|
||||||
wasm_write_to_console: (str, len) => { console.log(mem.readString(str, len)); },
|
wasm_write_to_console: (str, len) => { console.log(mem.decodeString(str, len)); },
|
||||||
};
|
};
|
||||||
const program = await WebAssembly['instantiate'](binary, { "env": import_table });
|
const program = await WebAssembly['instantiate'](binary, { "env": import_table });
|
||||||
const instance = program['instance'];
|
const instance = program['instance'];
|
||||||
@@ -107,146 +193,17 @@ async function WASMInit(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
const render = new wglRender();
|
||||||
const wasm = await WASMInit("main.wasm");
|
const wasm = await WASMInit("main.wasm");
|
||||||
wasm.exports.init();
|
wasm.exports.init();
|
||||||
|
|
||||||
|
function frameLoop() {
|
||||||
const VERTEX_SRC = `#version 300 es
|
render.updateTextureSize(wasm);
|
||||||
precision mediump float;
|
render.render();
|
||||||
in vec2 a_position;
|
requestAnimationFrame(frameLoop);
|
||||||
out vec2 v_texCoord;
|
|
||||||
void main() {
|
|
||||||
v_texCoord = (a_position + 1.0) * 0.5; // [0,1]
|
|
||||||
v_texCoord.y = (1.0 - v_texCoord.y); // Flip the Y to get the standard memory behavior
|
|
||||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const FRAGMENT_SRC = `#version 300 es
|
|
||||||
precision mediump float;
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
out vec4 outColor;
|
|
||||||
void main() {
|
|
||||||
outColor = texture(u_texture, v_texCoord);
|
|
||||||
}`;
|
|
||||||
|
|
||||||
/* ---------- WebGL setup ---------- */
|
|
||||||
const canvas = document.getElementById('glcanvas');
|
|
||||||
const gl = canvas.getContext('webgl2');
|
|
||||||
if (!gl) { alert('WebGL 2 not supported'); return; }
|
|
||||||
|
|
||||||
const vs = createShader(gl, gl.VERTEX_SHADER, VERTEX_SRC);
|
|
||||||
const fs = createShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SRC);
|
|
||||||
const program = createProgram(gl, vs, fs);
|
|
||||||
|
|
||||||
const posLoc = gl.getAttribLocation(program, 'a_position');
|
|
||||||
const texLoc = gl.getUniformLocation(program, 'u_texture');
|
|
||||||
|
|
||||||
// Fullscreen quad
|
|
||||||
const quad = new Float32Array([
|
|
||||||
-1, -1, // bottom left
|
|
||||||
1, -1, // bottom right
|
|
||||||
-1, 1, // top left
|
|
||||||
-1, 1, // top left
|
|
||||||
1, -1, // bottom right
|
|
||||||
1, 1 // top right
|
|
||||||
]);
|
|
||||||
|
|
||||||
const vao = gl.createVertexArray();
|
|
||||||
gl.bindVertexArray(vao);
|
|
||||||
const vbo = gl.createBuffer();
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
|
|
||||||
gl.enableVertexAttribArray(posLoc);
|
|
||||||
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
||||||
|
|
||||||
|
|
||||||
/* ---------- Dynamic texture handling ---------- */
|
|
||||||
let texture = null;
|
|
||||||
let texWidth = 0, texHeight = 0;
|
|
||||||
let pixels = null;
|
|
||||||
|
|
||||||
function createTexture(w, h) {
|
|
||||||
const tex = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
||||||
// Allocate an empty texture of the canvas size
|
|
||||||
gl.texImage2D(
|
|
||||||
gl.TEXTURE_2D, 0, gl.RGBA8,
|
|
||||||
w, h, 0,
|
|
||||||
gl.RGBA, gl.UNSIGNED_BYTE, null
|
|
||||||
);
|
|
||||||
// Set parameters – no filtering needed for empty data
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
||||||
return tex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTextureSize() {
|
requestAnimationFrame(frameLoop);
|
||||||
const dpr = window.devicePixelRatio || 1;
|
|
||||||
const w = Math.floor(canvas.clientWidth * dpr);
|
|
||||||
const h = Math.floor(canvas.clientHeight * dpr);
|
|
||||||
if (w !== texWidth || h !== texHeight) {
|
|
||||||
texWidth = w; texHeight = h;
|
|
||||||
if (texture) gl.deleteTexture(texture);
|
|
||||||
texture = createTexture(w, h);
|
|
||||||
|
|
||||||
let rawPtr = wasm.exports.update(texWidth, texHeight, window.devicePixelRatio || 1.0);
|
|
||||||
pixels = wasm.readUint8(rawPtr, texWidth*texHeight*4);
|
|
||||||
|
|
||||||
{
|
|
||||||
const canvas = document.getElementById('2dcanvas');
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
|
||||||
const w = Math.floor(canvas.clientWidth * dpr);
|
|
||||||
const h = Math.floor(canvas.clientHeight * dpr);
|
|
||||||
canvas.width = w;
|
|
||||||
canvas.height = h;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
let size = 100*dpr;
|
|
||||||
ctx.font = `${size}px FiraCode`;
|
|
||||||
const text = 'Hello world (canvas)';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
const centerX = canvas.width / 2;
|
|
||||||
const centerY = canvas.height / 2;
|
|
||||||
ctx.fillStyle = '#000';
|
|
||||||
ctx.fillText(text, centerX, centerY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Canvas resize routine ---------- */
|
|
||||||
function resizeCanvasToDisplaySize() {
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
|
||||||
const w = Math.floor(canvas.clientWidth * dpr);
|
|
||||||
const h = Math.floor(canvas.clientHeight * dpr);
|
|
||||||
if (canvas.width !== w || canvas.height !== h) {
|
|
||||||
canvas.width = w;
|
|
||||||
canvas.height = h;
|
|
||||||
gl.viewport(0, 0, w, h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Render loop ---------- */
|
|
||||||
function render() {
|
|
||||||
resizeCanvasToDisplaySize();
|
|
||||||
updateTextureSize();
|
|
||||||
|
|
||||||
|
|
||||||
gl.useProgram(program);
|
|
||||||
gl.bindVertexArray(vao);
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
||||||
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, texWidth, texHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
||||||
gl.uniform1i(texLoc, 0);
|
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
||||||
|
|
||||||
requestAnimationFrame(render);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(render);
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
#include "core/core.c"
|
#include "core/core.c"
|
||||||
|
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
#define STBTT_ifloor(x) ((int)f64_floor(x))
|
#define STBTT_ifloor(x) ((i32)f64_floor(x))
|
||||||
#define STBTT_iceil(x) ((int)f64_ceil(x))
|
#define STBTT_iceil(x) ((i32)f64_ceil(x))
|
||||||
#define STBTT_sqrt(x) (f64_sqrt(x))
|
#define STBTT_sqrt(x) (f64_sqrt(x))
|
||||||
#define STBTT_fmod(x,y) (f64_mod(x,y))
|
#define STBTT_fmod(x,y) (f64_mod(x,y))
|
||||||
#define STBTT_pow(x,y) (f64_pow(x,y))
|
#define STBTT_pow(x,y) (f64_pow(x,y))
|
||||||
@@ -20,32 +20,123 @@
|
|||||||
#include "vendor/stb/stb_truetype.h"
|
#include "vendor/stb/stb_truetype.h"
|
||||||
#include "fira_code.c"
|
#include "fira_code.c"
|
||||||
|
|
||||||
typedef struct glyph_t glyph_t;
|
|
||||||
struct glyph_t {
|
|
||||||
u8 *data;
|
|
||||||
i32 width, height;
|
|
||||||
i32 xoff, yoff;
|
|
||||||
i32 left_side_bearing, xadvance;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct font_t font_t;
|
|
||||||
struct font_t {
|
|
||||||
glyph_t glyphs[96];
|
|
||||||
i32 ascent, descent, line_gap, size;
|
|
||||||
f32 scale;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct bitmap_t bitmap_t;
|
typedef struct bitmap_t bitmap_t;
|
||||||
struct bitmap_t {
|
struct bitmap_t {
|
||||||
u32 *data;
|
u32 *data;
|
||||||
i32 x, y;
|
i32 x, y;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct glyph_t glyph_t;
|
||||||
|
struct glyph_t {
|
||||||
|
bitmap_t bitmap;
|
||||||
|
i32 xoff, yoff;
|
||||||
|
i32 left_side_bearing, xadvance;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct font_t font_t;
|
||||||
|
struct font_t {
|
||||||
|
glyph_t glyphs[256];
|
||||||
|
bitmap_t atlas;
|
||||||
|
i32 ascent, descent, line_gap, size;
|
||||||
|
f32 scale;
|
||||||
|
i32 height;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn font_t create_font(i32 size) {
|
||||||
|
font_t font = {0};
|
||||||
|
font.size = size;
|
||||||
|
stbtt_fontinfo stb_font;
|
||||||
|
i32 rc = stbtt_InitFont(&stb_font, main_font_data, 0);
|
||||||
|
assert_expr(rc != 0);
|
||||||
|
|
||||||
|
font.scale = stbtt_ScaleForPixelHeight(&stb_font, (f32)font.size);
|
||||||
|
stbtt_GetFontVMetrics(&stb_font, &font.ascent, &font.descent, &font.line_gap);
|
||||||
|
font.height = f32_round((font.ascent - font.descent) + font.line_gap) * font.scale;
|
||||||
|
for (i32 c = 0; c < 256; c += 1) {
|
||||||
|
glyph_t *glyph = font.glyphs + (c - ' ');
|
||||||
|
u8 *temp_data = (u8 *)stbtt_GetCodepointBitmap(&stb_font, 0, font.scale, c, &glyph->bitmap.x, &glyph->bitmap.y, &glyph->xoff, &glyph->yoff);
|
||||||
|
glyph->bitmap.data = ma_push_array(&tcx->temp, u32, glyph->bitmap.x * glyph->bitmap.y);
|
||||||
|
for (i32 y = 0; y < glyph->bitmap.y; y += 1) {
|
||||||
|
for (i32 x = 0; x < glyph->bitmap.x; x += 1) {
|
||||||
|
glyph->bitmap.data[x + y * glyph->bitmap.x] = temp_data[x + y * glyph->bitmap.x] << 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stbtt_GetCodepointHMetrics(&stb_font, c, &glyph->xadvance, &glyph->left_side_bearing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn font_t create_font_atlas(i32 size) {
|
||||||
|
stbtt_fontinfo stb_font;
|
||||||
|
i32 rc = stbtt_InitFont(&stb_font, main_font_data, 0);
|
||||||
|
assert_expr(rc != 0);
|
||||||
|
|
||||||
|
font_t font = { 0 };
|
||||||
|
font.size = size;
|
||||||
|
font.atlas.x = 512;
|
||||||
|
font.atlas.y = 512;
|
||||||
|
font.atlas.data = ma_push_array(&tcx->temp, u32, font.atlas.x * font.atlas.y);
|
||||||
|
font.scale = stbtt_ScaleForPixelHeight(&stb_font, (f32)font.size);
|
||||||
|
stbtt_GetFontVMetrics(&stb_font, &font.ascent, &font.descent, &font.line_gap);
|
||||||
|
|
||||||
|
i32 xiter = 2;
|
||||||
|
i32 yiter = 2;
|
||||||
|
i32 max_yiter_for_row = 0;
|
||||||
|
for (i32 c = ' '; c < '~'; c += 1) {
|
||||||
|
glyph_t *glyph = font.glyphs + (c - ' ');
|
||||||
|
u8 *temp_data = (u8 *)stbtt_GetCodepointBitmap(&stb_font, 0, font.scale, c, &glyph->bitmap.x, &glyph->bitmap.y, &glyph->xoff, &glyph->yoff);
|
||||||
|
if (xiter + glyph->bitmap.x >= font.atlas.x) {
|
||||||
|
xiter = 0;
|
||||||
|
yiter += max_yiter_for_row + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
max_yiter_for_row = MAX(max_yiter_for_row, glyph->bitmap.y);
|
||||||
|
for (i32 y = 0; y < glyph->bitmap.y; y += 1) {
|
||||||
|
for (i32 x = 0; x < glyph->bitmap.x; x += 1) {
|
||||||
|
font.atlas.data[(xiter + x) + (yiter + y)*font.atlas.x] = temp_data[x + y * glyph->bitmap.x] << 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xiter += glyph->bitmap.x + 2;
|
||||||
|
}
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
fn_wasm_export void init(void) {
|
fn_wasm_export void init(void) {
|
||||||
os_core_init();
|
os_core_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn v2i32_t draw_string(bitmap_t *canvas, font_t *font, int ix, int iy, b32 draw, char *string) {
|
fn void draw_rect(bitmap_t *canvas, bitmap_t *bitmap, i32 x, i32 y) {
|
||||||
|
i32 x0 = x;
|
||||||
|
i32 y0 = y;
|
||||||
|
i32 x1 = x + bitmap->x;
|
||||||
|
i32 y1 = y + bitmap->y;
|
||||||
|
|
||||||
|
i32 cx0 = CLAMP(x0, 0, canvas->x);
|
||||||
|
i32 cy0 = CLAMP(y0, 0, canvas->y);
|
||||||
|
i32 cx1 = CLAMP(x1, 0, canvas->x);
|
||||||
|
i32 cy1 = CLAMP(y1, 0, canvas->y);
|
||||||
|
|
||||||
|
|
||||||
|
if (cx0 >= cx1 || cy0 >= cy1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
i32 sx_off = cx0 - x0;
|
||||||
|
i32 sy_off = cy0 - y0;
|
||||||
|
|
||||||
|
i32 sy = sy_off;
|
||||||
|
for (i32 y = cy0; y < cy1; y += 1) {
|
||||||
|
i32 sx = sx_off;
|
||||||
|
for (i32 x = cx0; x < cx1; x += 1) {
|
||||||
|
canvas->data[x + y * canvas->x] = bitmap->data[sx + sy * bitmap->x];
|
||||||
|
sx += 1;
|
||||||
|
}
|
||||||
|
sy += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn v2i32_t draw_string(bitmap_t *canvas, font_t *font, i32 ix, i32 iy, b32 draw, char *string) {
|
||||||
i32 xiter = 0;
|
i32 xiter = 0;
|
||||||
v2i32_t R = {0};
|
v2i32_t R = {0};
|
||||||
for (char *it = string; *it; it += 1) {
|
for (char *it = string; *it; it += 1) {
|
||||||
@@ -53,60 +144,25 @@ fn v2i32_t draw_string(bitmap_t *canvas, font_t *font, int ix, int iy, b32 draw,
|
|||||||
|
|
||||||
i32 curr_xiter = xiter;
|
i32 curr_xiter = xiter;
|
||||||
xiter += g->xadvance * font->scale;
|
xiter += g->xadvance * font->scale;
|
||||||
|
|
||||||
i32 x0 = curr_xiter + g->xoff + ix;
|
|
||||||
i32 y0 = g->yoff + font->ascent*font->scale + iy;
|
|
||||||
i32 x1 = g->width + x0;
|
|
||||||
i32 y1 = g->height + y0;
|
|
||||||
|
|
||||||
i32 cx0 = CLAMP(x0, 0, canvas->x);
|
|
||||||
i32 cy0 = CLAMP(y0, 0, canvas->y);
|
|
||||||
i32 cx1 = CLAMP(x1, 0, canvas->x);
|
|
||||||
i32 cy1 = CLAMP(y1, 0, canvas->y);
|
|
||||||
|
|
||||||
R.x = xiter;
|
R.x = xiter;
|
||||||
R.y = MAX(R.y, g->height);
|
R.y = MAX(R.y, g->bitmap.y);
|
||||||
|
|
||||||
if (!draw || (cx0 >= cx1 || cy0 >= cy1)) {
|
if (!draw) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
i32 sx_off = cx0 - x0;
|
draw_rect(canvas, &g->bitmap, curr_xiter + g->xoff + ix, g->yoff + font->ascent*font->scale + iy);
|
||||||
i32 sy_off = cy0 - y0;
|
|
||||||
|
|
||||||
i32 sy = sy_off;
|
|
||||||
for (i32 y = cy0; y < cy1; y += 1) {
|
|
||||||
i32 sx = sx_off;
|
|
||||||
for (i32 x = cx0; x < cx1; x += 1) {
|
|
||||||
canvas->data[x + y * canvas->x] = g->data[sx + sy * g->width] << 24;
|
|
||||||
sx += 1;
|
|
||||||
}
|
|
||||||
sy += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return R;
|
return R;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn_wasm_export u32 *update(i32 width, i32 height, f32 dpr) {
|
fn_wasm_export u32 *update(i32 width, i32 height, f32 dpr) {
|
||||||
gb font_t font = {0};
|
|
||||||
font.size = (i32)(130.f * dpr);
|
|
||||||
stbtt_fontinfo stb_font;
|
|
||||||
int rc = stbtt_InitFont(&stb_font, main_font_data, stbtt_GetFontOffsetForIndex(main_font_data,0));
|
|
||||||
assert_expr(rc != 0);
|
|
||||||
|
|
||||||
font.scale = stbtt_ScaleForPixelHeight(&stb_font, (f32)font.size);
|
|
||||||
stbtt_GetFontVMetrics(&stb_font, &font.ascent, &font.descent, &font.line_gap);
|
|
||||||
for (int c = ' '; c < '~'; c += 1) {
|
|
||||||
glyph_t *glyph = font.glyphs + (c - ' ');
|
|
||||||
u8 *temp_data = (u8 *)stbtt_GetCodepointBitmap(&stb_font, 0, font.scale, c, &glyph->width, &glyph->height, &glyph->xoff, &glyph->yoff);
|
|
||||||
glyph->data = ma_push_array_copy(&tcx->perm, temp_data, glyph->width*glyph->height);
|
|
||||||
stbtt_GetCodepointHMetrics(&stb_font, c, &glyph->xadvance, &glyph->left_side_bearing);
|
|
||||||
}
|
|
||||||
|
|
||||||
ma_set0(&tcx->temp);
|
ma_set0(&tcx->temp);
|
||||||
u32 *pixels = ma_push_array(&tcx->temp, u32, width * height);
|
u32 *pixels = ma_push_array(&tcx->temp, u32, width * height);
|
||||||
bitmap_t canvas = {pixels, width, height};
|
bitmap_t canvas = {pixels, width, height};
|
||||||
v2i32_t size = draw_string(&canvas, &font, 200, 500, false, "Hello world (stb)");
|
font_t font = create_font_atlas(60);
|
||||||
draw_string(&canvas, &font, (width-size.x)/2, height - size.y*2, true, "Hello world (stb)");
|
draw_rect(&canvas, &font.atlas, 0, 0);
|
||||||
|
//v2i32_t size = draw_string(&canvas, &font, 200, 500, false, "Hello world (stb)");
|
||||||
|
//draw_string(&canvas, &font, (width-size.x)/2, height - size.y*2, true, "Hello world (stb)");
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user