pre-git state

This commit is contained in:
Kevin Trogant 2023-10-11 22:23:26 +02:00
commit d8e4480819
20 changed files with 14095 additions and 0 deletions

18
.clang-format Normal file
View File

@ -0,0 +1,18 @@
IndentWidth: 4
AlignAfterOpenBracket: Align
AlignArrayOfStructures: Right
AlignConsecutiveAssignments: true
AlignConsecutiveMacros: true
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: Never
AlwaysBreakAfterReturnType: None
BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: Attach
IndentPPDirectives: BeforeHash
PointerAlignment: Right

1819
contrib/glad/glad.c Normal file

File diff suppressed because it is too large Load Diff

3656
contrib/glad/glad.h Normal file

File diff suppressed because it is too large Load Diff

311
contrib/glad/khrplatform.h Normal file
View File

@ -0,0 +1,311 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
/*
* To support platform where unsigned long cannot be used interchangeably with
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
* unsigned long long or similar (this results in different C++ name mangling).
* To avoid changes for existing platforms, we restrict usage of intptr_t to
* platforms where the size of a pointer is larger than the size of long.
*/
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
#define KHRONOS_USE_INTPTR_T
#endif
#endif
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef KHRONOS_USE_INTPTR_T
typedef intptr_t khronos_intptr_t;
typedef uintptr_t khronos_uintptr_t;
#elif defined(_WIN64)
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
#endif
#if defined(_WIN64)
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

26
contrib/xxhash/LICENSE Normal file
View File

@ -0,0 +1,26 @@
xxHash Library
Copyright (c) 2012-2021 Yann Collet
All rights reserved.
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

43
contrib/xxhash/xxhash.c Normal file
View File

@ -0,0 +1,43 @@
/*
* xxHash - Extremely Fast Hash algorithm
* Copyright (C) 2012-2021 Yann Collet
*
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You can contact the author at:
* - xxHash homepage: https://www.xxhash.com
* - xxHash source repository: https://github.com/Cyan4973/xxHash
*/
/*
* xxhash.c instantiates functions defined in xxhash.h
*/
#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */
#define XXH_IMPLEMENTATION /* access definitions */
#include "xxhash.h"

6773
contrib/xxhash/xxhash.h Normal file

File diff suppressed because it is too large Load Diff

49
include/fio.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef VY_FIO_H
#define VY_FIO_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "voyage.h"
typedef struct {
void *data;
size_t size;
} vy_file_buffer;
/* used to identify a file (XXH3 hash of the path) */
typedef uint64_t vy_file_id;
typedef unsigned int vy_fio_handle;
typedef struct {
unsigned int queue_size;
unsigned int max_file_count;
} vy_fio_config;
bool vyInitFIO(const vy_fio_config *config);
void vyShutdownFIO(void);
vy_file_id vyGetFileId(const char *path);
vy_file_id vyGetFileIdFromSpan(vy_text_span path);
vy_file_id vyAddFile(const char *path);
vy_file_id vyAddFileFromSpan(vy_text_span path);
const char *vyGetFilePath(vy_file_id fid);
vy_fio_handle vyEnqueueRead(vy_file_id fid);
void vyAbortFIO(vy_fio_handle fio);
bool vyIsFIOFinished(vy_fio_handle fio);
bool vyRetrieveReadBuffer(vy_fio_handle fio, vy_file_buffer *buffer);
void vyFreeFileBuffer(vy_file_buffer buffer);
#endif

56
include/gfx.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef VY_GFX_H
#define VY_GFX_H
#include <stdint.h>
/* graphics system. this is the interface of the rendering code.
*
* we need (at least) three different renderers:
* - world cell renderer (for world & dungeon environments)
* - character renderer (for animated models)
* - object renderer (for static models)
*/
#include <stdbool.h>
bool vyInitRenderer(void);
void vyShutdownRenderer(void);
/* Generational indices for backend objects */
typedef struct {
uint32_t index;
} vy_gfx_pipeline_id;
#define VY_IS_GFX_ID_VALID(id) ((id).index != 0)
/* Attributes are used to bind buffers (or textures) to symbolic values.
* For example, an attribute might be bound to "CELL_GRID", which would be
* replaced with the (at the time of the invoke) grid buffer of the current
* world cell.
*/
typedef enum {
VY_ATTRIBUTE_VALUE_UNDEFINED,
VY_ATTRIBUTE_VALUE_MATERIAL_ALBEDO,
} vy_attribute_value;
typedef struct {
uint32_t index;
vy_attribute_value value;
} vy_attribute_binding;
typedef struct {
vy_attribute_binding *uniform_bindings;
vy_attribute_binding *storage_bindings;
vy_attribute_binding *texture_bindings;
vy_gfx_pipeline_id pipeline;
unsigned int uniform_binding_count;
unsigned int storage_binding_count;
unsigned int texture_binding_count;
} vy_shader;
#endif

29
include/gfx_backend.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef VY_GFX_BACKEND_H
#define VY_GFX_BACKEND_H
/* Backend functions and types. */
#include <stddef.h>
#include "gfx.h"
typedef struct {
const char *compute_source;
size_t compute_source_length;
} vy_compute_pipeline_info;
typedef struct {
const char *vertex_source;
size_t vertex_source_length;
const char *fragment_source;
size_t fragment_source_length;
} vy_graphics_pipeline_info;
vy_gfx_pipeline_id
vyCompileComputePipeline(const vy_compute_pipeline_info *info);
vy_gfx_pipeline_id
vyCompileGraphicsPipeline(const vy_graphics_pipeline_info *info);
#endif

17
include/voyage.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef VY_VOYAGE_H
#define VY_VOYAGE_H
/* basic types and macros */
#include <stddef.h>
#define VY_UNUSED(x) ((void)sizeof((x)))
typedef struct {
const char *start;
unsigned int length;
} vy_text_span;
void vyReportError(const char *subsystem, const char *fmt, ...);
#endif

51
meson.build Normal file
View File

@ -0,0 +1,51 @@
project('voyage', 'c',
default_options: ['buildtype=debug', 'b_sanitize=address', 'c_std=c17', 'warning_level=3'])
compiler = meson.get_compiler('c')
buildtype = get_option('buildtype')
# Build options
if compiler.get_argument_syntax() == 'gcc'
add_project_arguments(
['-Wconversion', '-Wno-sign-conversion',
'-Wdouble-promotion', '-Wno-unused-function', '-Wno-unused-parameter'],
language : 'c'
)
elif compiler.get_argument_syntax() == 'msvc'
add_project_arguments(
['/wd4146', '/wd4245', '/wd4100', '/D_CRT_SECURE_NO_WARNINGS', '/RTcsu'],
language: 'c'
)
endif
# Debug specific flags
if buildtype == 'debug'
add_project_arguments([ '-DDEBUG'], language : 'c')
endif
# "Select" renderer backend
# Currently only OpenGL is supported
add_project_arguments([ '-DRENDERER_GL'], language : 'c')
# Gather dependencies
thread_dep = dependency('threads')
m_dep = compiler.find_library('m', required : false)
glfw_proj = subproject('glfw')
glfw_dep = glfw_proj.get_variable('glfw_dep')
incdir = include_directories(['contrib', 'include'])
executable('voyage',
# Project Sources
'src/voyage.c',
'src/fio.c',
'src/error_report.c',
'src/gfx_main.c',
'src/gfx_shader_loading.c',
'src/gfx_pipelines.c',
# Contrib Sources
'contrib/glad/glad.c',
'contrib/xxhash/xxhash.c',
dependencies : [thread_dep, m_dep, glfw_dep],
include_directories: incdir)

7
shader/cell.shader Normal file
View File

@ -0,0 +1,7 @@
vertex shader/cell_vert.glsl;
fragment shader/cell_frag.glsl;
texture_bindings {
0 MATERIAL_ALBEDO;
1 MATERIAL_NORMAL;
}

15
src/error_report.c Normal file
View File

@ -0,0 +1,15 @@
#include <stdarg.h>
#include <stdio.h>
#include "voyage.h"
/* TODO(Kevin): Log to file, show error message box, ... */
void vyReportError(const char *subsystem, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[%s] ", subsystem);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}

358
src/fio.c Normal file
View File

@ -0,0 +1,358 @@
#include "fio.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <xxhash/xxhash.h>
#ifdef __linux__
#include <pthread.h>
#endif
#define FLAGS_FINISHED 0x001
#define FLAGS_RETRIEVED 0x002
#define FLAGS_IN_USE 0x004
typedef struct {
vy_file_id fid;
vy_file_buffer buffer;
unsigned int flags;
} vy_file_op;
/* Ringbuffer of file io operations */
typedef struct {
vy_file_op *ops;
unsigned int size;
unsigned int write_pos;
unsigned int read_pos;
#ifdef __linux__
pthread_mutex_t mutex;
pthread_cond_t pending_cond;
#endif
} vy_fio_queue;
#define NAME_CAP(cap) ((cap)*128)
typedef struct {
vy_file_id *ids;
unsigned int *name_offsets;
char *names;
unsigned int capacity;
unsigned int name_head;
#ifdef __linux__
pthread_mutex_t mutex;
#endif
} vy_file_tab;
static vy_fio_queue _queue;
static vy_file_tab _file_tab;
#ifdef __linux__
static pthread_t _thread;
#endif
static bool InitFIOQueue(unsigned int size) {
_queue.ops = calloc(size, sizeof(vy_file_op));
if (!_queue.ops)
return false;
_queue.write_pos = 0;
_queue.read_pos = 0;
_queue.size = size;
return true;
}
static void ShutdownFIOQueue(void) {
for (unsigned int i = 0; i < _queue.size; ++i) {
free(_queue.ops[i].buffer.data);
}
free(_queue.ops);
}
static bool InitFileTab(unsigned int max_files) {
_file_tab.ids = calloc(max_files, sizeof(vy_file_id));
if (!_file_tab.ids)
return false;
_file_tab.name_offsets = calloc(max_files, sizeof(unsigned int));
if (!_file_tab.name_offsets)
return false;
_file_tab.names = malloc(NAME_CAP(max_files));
if (!_file_tab.names)
return false;
_file_tab.capacity = max_files;
_file_tab.name_head = 0;
#ifdef __linux__
if (pthread_mutex_init(&_file_tab.mutex, NULL) != 0)
return false;
#endif
return true;
}
static void ShutdownFileTab(void) {
free(_file_tab.ids);
free(_file_tab.names);
free(_file_tab.name_offsets);
#ifdef __linux__
pthread_mutex_destroy(&_file_tab.mutex);
#endif
}
static void *FIOThreadProc(void *);
bool vyInitFIO(const vy_fio_config *config) {
unsigned int queue_size = (config->queue_size) ? config->queue_size : 512;
unsigned int max_file_count =
(config->max_file_count) ? config->max_file_count : 512;
if (!InitFIOQueue(queue_size))
return false;
if (!InitFileTab(max_file_count))
return false;
#ifdef __linux__
if (pthread_create(&_thread, NULL, FIOThreadProc, NULL) != 0)
return false;
#endif
return true;
}
void vyShutdownFIO(void) {
#ifdef __linux__
pthread_cancel(_thread);
pthread_join(_thread, NULL);
#endif
ShutdownFIOQueue();
ShutdownFileTab();
}
vy_file_id vyGetFileId(const char *path) {
vy_text_span span;
span.start = path;
span.length = (unsigned int)strlen(path);
return vyGetFileIdFromSpan(span);
}
vy_file_id vyGetFileIdFromSpan(vy_text_span path) {
/* Randomly choosen, aka finger smash keyboard */
XXH64_hash_t seed = 15340978;
vy_file_id fid = (vy_file_id)XXH64(path.start, path.length, seed);
if (fid == 0)
fid = ~fid;
return fid;
}
vy_file_id vyAddFileFromSpan(vy_text_span path) {
vy_file_id fid = vyGetFileIdFromSpan(path);
#ifdef __linux__
pthread_mutex_lock(&_file_tab.mutex);
#endif
/* Hash Insert */
unsigned int i = 0;
unsigned int base = (unsigned int)(fid % _file_tab.capacity);
while (i < _file_tab.capacity) {
unsigned int at = (base + i) % _file_tab.capacity;
if (_file_tab.ids[at] == 0) {
/* Insert */
unsigned int slen = (unsigned int)path.length + 1;
if ((_file_tab.name_head + slen) >= NAME_CAP(_file_tab.capacity)) {
/* Out of name storage */
fid = 0;
break;
}
memcpy(_file_tab.names + _file_tab.name_head, path.start, slen);
_file_tab.name_offsets[at] = _file_tab.name_head;
_file_tab.ids[at] = fid;
_file_tab.name_head += slen;
break;
} else if (_file_tab.ids[at] == fid) {
break;
}
++i;
}
/* Out of space */
if (i == _file_tab.capacity)
fid = 0;
#ifdef __linux__
pthread_mutex_unlock(&_file_tab.mutex);
#endif
return fid;
}
vy_file_id vyAddFile(const char *path) {
vy_text_span span;
span.start = path;
span.length = (unsigned int)strlen(path);
return vyAddFileFromSpan(span);
}
const char *vyGetFilePath(vy_file_id fid) {
/* Hash Lookup */
if (fid == 0)
return NULL;
#ifdef __linux__
pthread_mutex_lock(&_file_tab.mutex);
#endif
const char *result = NULL;
unsigned int i = 0;
unsigned int base = (unsigned int)(fid % _file_tab.capacity);
while (i < _file_tab.capacity) {
unsigned int at = (base + i) % _file_tab.capacity;
if (_file_tab.ids[at] == fid) {
result = _file_tab.names + _file_tab.name_offsets[at];
break;
} else if (_file_tab.ids[at] == 0) {
break;
}
++i;
}
#ifdef __linux__
pthread_mutex_unlock(&_file_tab.mutex);
#endif
return result;
}
vy_fio_handle vyEnqueueRead(vy_file_id fid) {
vy_fio_handle handle = 0;
do {
#ifdef __linux__
pthread_mutex_lock(&_queue.mutex);
#endif
if (_queue.ops[_queue.write_pos].flags == 0 ||
((_queue.ops[_queue.write_pos].flags & FLAGS_FINISHED) != 0 &&
(_queue.ops[_queue.write_pos].flags & FLAGS_RETRIEVED) != 0)) {
_queue.ops[_queue.write_pos].fid = fid;
_queue.ops[_queue.write_pos].flags = FLAGS_IN_USE;
handle = _queue.write_pos + 1;
_queue.write_pos = (_queue.write_pos + 1) % _queue.size;
#ifdef __linux__
pthread_cond_signal(&_queue.pending_cond);
pthread_mutex_unlock(&_queue.mutex);
#endif
break;
}
#ifdef __linux__
pthread_mutex_unlock(&_queue.mutex);
#endif
} while (1);
return handle;
}
void vyAbortFIO(vy_fio_handle fio) {
if (fio == 0)
return;
#ifdef __linux__
pthread_mutex_lock(&_queue.mutex);
#endif
_queue.ops[fio - 1].flags = 0;
if (_queue.ops[fio - 1].buffer.data) {
free(_queue.ops[fio - 1].buffer.data);
_queue.ops[fio - 1].buffer.size = 0;
}
#ifdef __linux__
pthread_mutex_unlock(&_queue.mutex);
#endif
}
bool vyIsFIOFinished(vy_fio_handle fio) {
if (fio == 0)
return false;
#ifdef __linux__
pthread_mutex_lock(&_queue.mutex);
#endif
bool result = (_queue.ops[fio - 1].flags & FLAGS_FINISHED) != 0;
#ifdef __linux__
pthread_mutex_unlock(&_queue.mutex);
#endif
return result;
}
bool vyRetrieveReadBuffer(vy_fio_handle fio, vy_file_buffer *buffer) {
if (fio == 0)
return false;
#ifdef __linux__
pthread_mutex_lock(&_queue.mutex);
#endif
bool is_finished = (_queue.ops[fio - 1].flags & FLAGS_FINISHED) != 0;
if (is_finished) {
size_t sz = _queue.ops[fio - 1].buffer.size;
buffer->data = malloc(sz);
if (!buffer->data)
return false;
buffer->size = sz;
memcpy(buffer->data, _queue.ops[fio - 1].buffer.data, sz);
_queue.ops[fio - 1].flags |= FLAGS_RETRIEVED;
}
#ifdef __linux__
pthread_mutex_unlock(&_queue.mutex);
#endif
return is_finished;
}
void vyFreeFileBuffer(vy_file_buffer buffer) {
free(buffer.data);
}
static void ProcessRead(vy_file_op *op) {
const char *path = vyGetFilePath(op->fid);
if (!path) {
op->flags |= FLAGS_FINISHED;
return;
}
op->buffer.data = NULL;
op->buffer.size = 0;
FILE *file = fopen(path, "rb");
if (!file) {
op->flags |= FLAGS_FINISHED;
return;
}
fseek(file, 0, SEEK_END);
long fsz = ftell(file);
fseek(file, 0, SEEK_SET);
op->buffer.data = malloc(fsz);
op->buffer.size = (size_t)fsz;
if (fread(op->buffer.data, fsz, 1, file) != 1) {
free(op->buffer.data);
op->buffer.data = NULL;
op->buffer.size = 0;
}
fclose(file);
op->flags |= FLAGS_FINISHED;
}
static void *FIOThreadProc(void *_param) {
#ifdef __linux__
while (true) {
pthread_mutex_lock(&_queue.mutex);
while (_queue.write_pos == _queue.read_pos) {
pthread_cond_wait(&_queue.pending_cond, &_queue.mutex);
}
ProcessRead(&_queue.ops[_queue.read_pos]);
_queue.read_pos = (_queue.read_pos + 1) % _queue.size;
pthread_mutex_unlock(&_queue.mutex);
}
#endif
return NULL;
}

23
src/gfx_main.c Normal file
View File

@ -0,0 +1,23 @@
#include <glad/glad.h>
#include "gfx.h"
/* Attributes are used to bind buffers (or textures) to symbolic values.
* For example, an attribute might be bound to "CELL_GRID", which would be
* replaced with the (at the time of the invoke) grid buffer of the current
* world cell.
*/
bool vyLoadShaders(const char **paths, vy_shader *shaders, unsigned int count);
bool vyInitRenderer(void) {
/* Init shader programs */
const char *shader_files[] = {"shader/cell.shader"};
vy_shader shaders[1];
if (!vyLoadShaders(shader_files, shaders, 1))
return false;
return true;
}
void vyShutdownRenderer(void) {
}

155
src/gfx_pipelines.c Normal file
View File

@ -0,0 +1,155 @@
#include <glad/glad.h>
#include "gfx.h"
#include "gfx_backend.h"
#include "voyage.h"
typedef struct {
GLuint prog;
} vy_gl_pipeline;
#define NUM_SLOTS 256
typedef struct {
uint32_t generation_in_use[NUM_SLOTS];
vy_gl_pipeline pipelines[NUM_SLOTS];
} vy_pipeline_storage;
static vy_pipeline_storage _storage;
static vy_gfx_pipeline_id StorePipeline(vy_gl_pipeline pipeline) {
/* Search for free slot */
uint32_t slot = NUM_SLOTS;
for (uint32_t i = 0; i < NUM_SLOTS; ++i) {
if ((_storage.generation_in_use[i] & 0x1) == 0) {
slot = i;
break;
}
}
if (slot == NUM_SLOTS) {
vyReportError("GL_GFX", "Ran out of pipeline storage slots");
return (vy_gfx_pipeline_id){0};
}
uint32_t generation = _storage.generation_in_use[slot] >> 1;
generation = (generation + 1) & 0x1;
_storage.pipelines[slot] = pipeline;
_storage.generation_in_use[slot] = (generation << 1) | 0x1;
vy_gfx_pipeline_id id;
id.index = (generation << 27) | slot;
return id;
}
static void ReleasePipelineSlot(vy_gfx_pipeline_id id) {
uint32_t slot = id.index & 0x08ffffff;
uint32_t gen = (id.index >> 27) & 0x1f;
if (slot >= NUM_SLOTS)
return;
gen = gen << 1 | 0x1;
if (_storage.generation_in_use[slot] == gen)
_storage.generation_in_use[slot] &= ~0x1;
}
vy_gfx_pipeline_id
vyCompileComputePipeline(const vy_compute_pipeline_info *info) {
char info_log[512];
GLuint prog = glCreateProgram();
GLuint shader = glCreateShader(GL_COMPUTE_SHADER);
GLchar *code = (GLchar *)info->compute_source;
GLint code_len = (GLint)info->compute_source_length;
glShaderSource(shader, 1, (const GLchar **)&code, &code_len);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader, 512, NULL, info_log);
vyReportError("GL_GFX",
"Failed to compile compute shader\n%s",
info_log);
glDeleteProgram(prog);
glDeleteShader(shader);
return (vy_gfx_pipeline_id){0};
}
glAttachShader(prog, shader);
glLinkProgram(prog);
glGetProgramiv(prog, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(prog, 512, NULL, info_log);
vyReportError("GL_GFX", "Failed to link compute shader\n%s", info_log);
glDeleteShader(shader);
glDeleteProgram(prog);
return (vy_gfx_pipeline_id){0};
}
glDeleteShader(shader);
vy_gl_pipeline pipeline;
pipeline.prog = prog;
return StorePipeline(pipeline);
}
vy_gfx_pipeline_id
vyCompileGraphicsPipeline(const vy_graphics_pipeline_info *info) {
char info_log[512];
GLuint prog = glCreateProgram();
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
GLchar *code = (GLchar *)info->vertex_source;
GLint code_len = (GLint)info->vertex_source_length;
glShaderSource(vertex_shader, 1, (const GLchar **)&code, &code_len);
glCompileShader(vertex_shader);
GLint success;
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
vyReportError("GL_GFX",
"Failed to compile vertex shader\n%s",
info_log);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glDeleteProgram(prog);
return (vy_gfx_pipeline_id){0};
}
glAttachShader(prog, vertex_shader);
code = (GLchar *)info->fragment_source;
code_len = (GLint)info->fragment_source_length;
glShaderSource(fragment_shader, 1, (const GLchar **)&code, &code_len);
glCompileShader(fragment_shader);
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragment_shader, 512, NULL, info_log);
vyReportError("GFX", "Failed to compile fragment shader\n%s", info_log);
glDeleteShader(fragment_shader);
glDeleteShader(fragment_shader);
glDeleteProgram(prog);
return (vy_gfx_pipeline_id){0};
}
glAttachShader(prog, fragment_shader);
glLinkProgram(prog);
glGetProgramiv(prog, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(prog, 512, NULL, info_log);
vyReportError("GFX", "Failed to link graphics shader\n%s", info_log);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
glDeleteProgram(prog);
return (vy_gfx_pipeline_id){0};
}
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
vy_gl_pipeline pipeline;
pipeline.prog = prog;
return StorePipeline(pipeline);
}

563
src/gfx_shader_loading.c Normal file
View File

@ -0,0 +1,563 @@
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "fio.h"
#include "gfx.h"
#include "gfx_backend.h"
#include "voyage.h"
typedef enum {
VY_STMT_FORM_VALUE,
VY_STMT_FORM_LIST,
} vy_stmt_form;
typedef struct {
unsigned int first;
unsigned int count;
} vy_parsed_stmt_list;
typedef struct {
vy_stmt_form form;
vy_text_span attribute;
union {
vy_text_span value;
unsigned int list_index;
};
/* For lists */
unsigned int next;
} vy_parsed_stmt;
typedef struct {
const char *file;
const char *text;
size_t at;
size_t length;
int line;
vy_parsed_stmt *statements;
unsigned int statement_count;
unsigned int statement_capacity;
vy_parsed_stmt_list *statement_lists;
unsigned int statement_list_count;
unsigned int statement_list_capacity;
} vy_parse_state;
static bool IsAllowedChar(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') || (c == '.') || (c == '_') || (c == '/');
}
static bool IsWhitespace(char c) {
return c == ' ' || c == '\t' || c == '\n';
}
static void SkipWhitespace(vy_parse_state *state) {
while (state->at < state->length && IsWhitespace(state->text[state->at])) {
if (state->text[state->at] == '\n')
++state->line;
++state->at;
}
}
static bool ParseAttribute(vy_parse_state *state, vy_text_span *_name) {
vy_text_span name;
name.start = &state->text[state->at];
name.length = 0;
while (state->at < state->length && !IsWhitespace(state->text[state->at])) {
if (IsAllowedChar(state->text[state->at])) {
++state->at;
++name.length;
} else {
vyReportError("GFX",
"%s:%d Unexpected character %c",
state->file,
state->line,
state->text[state->at]);
return false;
}
}
*_name = name;
return true;
}
static bool ParseValue(vy_parse_state *state, vy_text_span *_value) {
vy_text_span value;
value.start = &state->text[state->at];
value.length = 0;
while (state->at < state->length && !IsWhitespace(state->text[state->at]) &&
state->text[state->at] != ';') {
if (IsAllowedChar(state->text[state->at])) {
++state->at;
++value.length;
} else {
vyReportError("GFX",
"%s:%d Unexpected character %c",
state->file,
state->line,
state->text[state->at]);
return false;
}
}
*_value = value;
return true;
}
static bool ParseStmtList(vy_parse_state *state, unsigned int *list_index);
static bool ParseStmt(vy_parse_state *state, unsigned int *stmt_index) {
vy_parsed_stmt stmt;
stmt.next = UINT_MAX; /* end of list */
SkipWhitespace(state);
if (!ParseAttribute(state, &stmt.attribute))
return false;
SkipWhitespace(state);
if (state->at == state->length) {
vyReportError("GFX",
"%s:%d Expected either a value or '{'",
state->file,
state->line);
return false;
}
if (state->text[state->at] == '{') {
/* Consume '{' */
++state->at;
stmt.form = VY_STMT_FORM_LIST;
if (!ParseStmtList(state, &stmt.list_index))
return false;
/* Consume '}' */
if (state->at < state->length && state->text[state->at] == '}')
++state->at;
} else {
stmt.form = VY_STMT_FORM_VALUE;
if (!ParseValue(state, &stmt.value))
return false;
/* Consume ';' */
if (state->at < state->length && state->text[state->at] == ';')
++state->at;
}
SkipWhitespace(state);
/* Add statement to array */
if (state->statement_count == state->statement_capacity) {
unsigned int cap = (state->statement_capacity > 0)
? state->statement_capacity * 2
: 64;
vy_parsed_stmt *temp =
realloc(state->statements, sizeof(vy_parsed_stmt) * cap);
if (!temp) {
vyReportError("GFX",
"While parsing %s: Out of memory\n",
state->file);
return false;
}
state->statements = temp;
state->statement_capacity = cap;
}
state->statements[state->statement_count] = stmt;
*stmt_index = state->statement_count++;
return true;
}
static bool ParseStmtList(vy_parse_state *state, unsigned int *list_index) {
vy_parsed_stmt_list list;
list.first = state->statement_count;
list.count = 0;
unsigned int last = UINT_MAX;
while (state->at < state->length && state->text[state->at] != '}') {
unsigned int stmt;
if (!ParseStmt(state, &stmt))
return false;
if (last != UINT_MAX)
state->statements[last].next = stmt;
last = stmt;
++list.count;
}
/* Add list to array */
if (state->statement_list_count == state->statement_list_capacity) {
unsigned int cap = (state->statement_list_capacity > 0)
? state->statement_list_capacity * 2
: 64;
vy_parsed_stmt_list *temp =
realloc(state->statement_lists, sizeof(vy_parsed_stmt_list) * cap);
if (!temp) {
vyReportError("GFX",
"While parsing %s: Out of memory\n",
state->file);
return false;
}
state->statement_lists = temp;
state->statement_list_capacity = cap;
}
state->statement_lists[state->statement_list_count] = list;
*list_index = state->statement_list_count++;
return true;
}
static void DbgPrintShaderFile(const vy_parse_state *state,
unsigned int list_index,
unsigned int indent) {
assert(list_index < state->statement_list_count);
const vy_parsed_stmt_list *list = &state->statement_lists[list_index];
unsigned int stmt_index = list->first;
for (unsigned int i = 0; i < list->count; ++i) {
const vy_parsed_stmt *stmt = &state->statements[stmt_index];
for (unsigned int j = 0; j < indent; ++j)
printf(" ");
printf("%.*s: ", stmt->attribute.length, stmt->attribute.start);
if (stmt->form == VY_STMT_FORM_VALUE) {
printf("%.*s\n", stmt->value.length, stmt->value.start);
} else {
printf("{\n");
DbgPrintShaderFile(state, stmt->list_index, indent + 2);
printf("}\n");
}
stmt_index = stmt->next;
}
assert(stmt_index = UINT_MAX);
}
static bool CompareSpanToString(vy_text_span span, const char *cmp) {
size_t cmp_len = strlen(cmp);
if (cmp_len != (size_t)span.length)
return false;
for (size_t i = 0; i < cmp_len; ++i) {
if (span.start[i] != cmp[i])
return false;
}
return true;
}
static const vy_parsed_stmt *FindStatement(const vy_parse_state *state,
unsigned int list_index,
const char *attribute) {
if (list_index >= state->statement_list_count)
return NULL;
const vy_parsed_stmt_list *list = &state->statement_lists[list_index];
unsigned int stmt_index = list->first;
for (unsigned int i = 0; i < list->count; ++i) {
const vy_parsed_stmt *stmt = &state->statements[stmt_index];
if (CompareSpanToString(stmt->attribute, attribute))
return stmt;
stmt_index = stmt->next;
}
return NULL;
}
static vy_fio_handle DispatchFileRead(vy_text_span path) {
vy_file_id fid = vyAddFileFromSpan(path);
if (fid == 0)
return 0;
vy_fio_handle fio = vyEnqueueRead(fid);
return fio;
}
static vy_fio_handle DispatchShaderRead(const char *shader,
const vy_parse_state *state,
unsigned int root_list,
const char *file_path) {
const vy_parsed_stmt *path = FindStatement(state, root_list, shader);
if (!path) {
return 0;
}
if (path->form != VY_STMT_FORM_VALUE) {
vyReportError("GFX",
"Expected simple value for attribute \"%s\" in %s\n",
shader,
file_path);
return 0;
}
return DispatchFileRead(path->value);
}
static vy_gfx_pipeline_id LinkProgram(vy_parse_state *state,
const char *file_path,
unsigned int root_list) {
/* Process the data */
vy_fio_handle vertex_read =
DispatchShaderRead("vertex", state, root_list, file_path);
vy_fio_handle fragment_read =
DispatchShaderRead("fragment", state, root_list, file_path);
vy_fio_handle compute_read =
DispatchShaderRead("compute", state, root_list, file_path);
if (compute_read) {
if (vertex_read || fragment_read) {
vyReportError("GFX",
"A shader program can not contain a compute "
"shader and graphics shaders: %s",
file_path);
vyAbortFIO(vertex_read);
vyAbortFIO(fragment_read);
vyAbortFIO(compute_read);
return (vy_gfx_pipeline_id){0};
}
while (!vyIsFIOFinished(compute_read)) {
/* wait */
}
vy_file_buffer compute_code;
if (!vyRetrieveReadBuffer(compute_read, &compute_code)) {
vyReportError("GFX",
"Failed to load compute shader required by: %s",
file_path);
return (vy_gfx_pipeline_id){0};
}
vy_compute_pipeline_info info;
info.compute_source = compute_code.data;
info.compute_source_length = compute_code.size;
vy_gfx_pipeline_id pipeline = vyCompileComputePipeline(&info);
vyFreeFileBuffer(compute_code);
return pipeline;
} else if (vertex_read || fragment_read) {
if (compute_read) {
vyReportError("GFX",
"A shader program can not contain a compute "
"shader and graphics shaders: %s",
file_path);
vyAbortFIO(vertex_read);
vyAbortFIO(fragment_read);
vyAbortFIO(compute_read);
return (vy_gfx_pipeline_id){0};
}
if (!vertex_read || !fragment_read) {
vyReportError("GFX",
"A shader program must contain at least a vertex and "
"a fragment shader: %s",
file_path);
vyAbortFIO(vertex_read);
vyAbortFIO(fragment_read);
vyAbortFIO(compute_read);
return (vy_gfx_pipeline_id){0};
}
vy_graphics_pipeline_info info;
vy_file_buffer vertex_code = {NULL, 0};
vy_file_buffer fragment_code = {NULL, 0};
int remaining = 2;
while (remaining > 0) {
if (vyIsFIOFinished(vertex_read)) {
if (!vyRetrieveReadBuffer(vertex_read, &vertex_code)) {
vyReportError(
"GFX",
"Failed to load vertex shader required by: %s",
file_path);
vyFreeFileBuffer(fragment_code);
return (vy_gfx_pipeline_id){0};
}
info.vertex_source = vertex_code.data;
info.vertex_source_length = vertex_code.size;
--remaining;
vertex_read = 0;
}
if (vyIsFIOFinished(fragment_read)) {
if (!vyRetrieveReadBuffer(fragment_read, &fragment_code)) {
vyReportError(
"GFX",
"Failed to load fragment shader required by: %s",
file_path);
vyFreeFileBuffer(vertex_code);
return (vy_gfx_pipeline_id){0};
}
info.fragment_source = fragment_code.data;
info.fragment_source_length = fragment_code.size;
--remaining;
fragment_read = 0;
}
}
vy_gfx_pipeline_id pipeline = vyCompileGraphicsPipeline(&info);
vyFreeFileBuffer(vertex_code);
vyFreeFileBuffer(fragment_code);
return pipeline;
} else if (!compute_read && !vertex_read && !fragment_read) {
vyReportError("GFX",
"Either \"compute\" or \"vertex\" and \"fragment\" "
"are required in %s",
file_path);
vyAbortFIO(vertex_read);
vyAbortFIO(fragment_read);
vyAbortFIO(compute_read);
}
return (vy_gfx_pipeline_id){0};
}
static bool ParseBindingIndex(vy_text_span span, unsigned int *index) {
if (span.length == 0)
return false;
int at = (int)span.length - 1;
unsigned int exp = 1;
unsigned int n = 0;
while (at >= 0) {
if (span.start[at] >= '0' && span.start[at] <= '9') {
unsigned int digit = (unsigned int)(span.start[at] - '0');
n += digit * exp;
} else {
vyReportError("GFX",
"Unexpected non-digit character in binding index");
return false;
}
--at;
exp *= 10;
}
*index = n;
return true;
}
static vy_attribute_value ParseBindingValue(vy_text_span span) {
if (CompareSpanToString(span, "MATERIAL_ALBEDO")) {
return VY_ATTRIBUTE_VALUE_MATERIAL_ALBEDO;
}
vyReportError("GFX",
"Unsupported binding value %*.s",
span.length,
span.start);
return VY_ATTRIBUTE_VALUE_UNDEFINED;
}
static bool
ParseShaderFile(vy_file_id fid, vy_file_buffer fbuf, vy_shader *shader) {
/* This is the grammar for shader files:
* <stmt-list> ::= <stmt>*
* <stmt> ::= <attribute> ( ( <value> ';' ) | ( '{' <stmt-list> '}' ) )
* <attribute> ::= [:alnum:]*
* <value>:: = [:alnum:]* */
const char *file_path = vyGetFilePath(fid);
vy_parse_state state = {.text = (const char *)fbuf.data,
.at = 0,
.length = fbuf.size,
.line = 1,
.file = file_path,
.statements = NULL,
.statement_lists = NULL,
.statement_capacity = 0,
.statement_list_capacity = 0};
bool result = true;
unsigned int root_list = 0;
if (!ParseStmtList(&state, &root_list)) {
result = false;
goto out;
}
DbgPrintShaderFile(&state, root_list, 0);
shader->pipeline = LinkProgram(&state, file_path, root_list);
if (!VY_IS_GFX_ID_VALID(shader->pipeline)) {
result = false;
goto out;
}
/* Process bindings */
shader->texture_bindings = NULL;
shader->texture_binding_count = 0;
shader->uniform_bindings = NULL;
shader->uniform_binding_count = 0;
shader->storage_bindings = NULL;
shader->storage_binding_count = 0;
const vy_parsed_stmt *texture_bindings =
FindStatement(&state, root_list, "texture_bindings");
if (texture_bindings) {
if (texture_bindings->form != VY_STMT_FORM_LIST) {
vyReportError("GFX",
"Expected list of bindings as the value of "
"\"texture_bindings\" in %s",
file_path);
goto out;
}
const vy_parsed_stmt_list *binding_list =
&state.statement_lists[texture_bindings->list_index];
shader->texture_bindings =
malloc(sizeof(vy_attribute_binding) * binding_list->count);
if (!shader->texture_bindings) {
vyReportError("GFX", "Out of memory");
goto out;
}
shader->texture_binding_count = binding_list->count;
unsigned int stmt_index = binding_list->first;
for (unsigned int i = 0; i < binding_list->count; ++i) {
const vy_parsed_stmt *stmt = &state.statements[stmt_index];
if (!ParseBindingIndex(stmt->attribute,
&shader->texture_bindings[i].index)) {
free(shader->texture_bindings);
result = false;
goto out;
}
shader->texture_bindings[i].value = ParseBindingValue(stmt->value);
if (shader->texture_bindings[i].value ==
VY_ATTRIBUTE_VALUE_UNDEFINED) {
free(shader->texture_bindings);
result = false;
goto out;
}
stmt_index = stmt->next;
}
}
const vy_parsed_stmt *uniform_bindings =
FindStatement(&state, root_list, "uniform_bindings");
const vy_parsed_stmt *storage_bindings =
FindStatement(&state, root_list, "storage_bindings");
out:
free(state.statements);
free(state.statement_lists);
return result;
}
bool vyLoadShaders(const char **paths, vy_shader *shaders, unsigned int count) {
vy_fio_handle fios[64];
vy_file_id fids[64];
for (unsigned int i = 0; i < count; i += 64) {
unsigned int chunk_size = count - i;
if (chunk_size > 64)
chunk_size = 64;
for (unsigned int j = 0; j < chunk_size; ++j) {
vy_file_id fid = vyAddFile(paths[i + j]);
if (!fid)
return false;
fids[j] = fid;
fios[j] = vyEnqueueRead(fid);
}
unsigned int remaining = chunk_size;
while (remaining > 0) {
for (unsigned int j = 0; j < chunk_size; ++j) {
if (vyIsFIOFinished(fios[j])) {
vy_file_buffer fbuf;
if (!vyRetrieveReadBuffer(fios[j], &fbuf)) {
return false;
}
ParseShaderFile(fids[j], fbuf, &shaders[i + j]);
vyFreeFileBuffer(fbuf);
--remaining;
}
}
}
}
return true;
}

114
src/voyage.c Normal file
View File

@ -0,0 +1,114 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "fio.h"
#include "gfx.h"
static void glfw_error_cb(int err, const char *description) {
fprintf(stderr, "[GLFW] %u: %s\n", err, description);
}
static void make_window_windowed_fullscreen(GLFWwindow *window,
GLFWmonitor *monitor) {
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(window,
monitor,
0,
0,
mode->width,
mode->height,
mode->refreshRate);
}
static void make_window_windowed(GLFWwindow *window, int w, int h) {
glfwSetWindowMonitor(window, NULL, 0, 0, w, h, GLFW_DONT_CARE);
}
typedef struct {
int f1;
int esc;
} key_states;
int main(int argc, char **argv) {
glfwSetErrorCallback(glfw_error_cb);
if (!glfwInit()) {
return 1;
}
printf("[GFLW] Version: %s\n", glfwGetVersionString());
/* Create a windowed full screen window */
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
GLFWwindow *window = glfwCreateWindow(640, 480, "Voyage", NULL, NULL);
if (!window) {
fprintf(stderr, "[GFX] Window creation failed.\n");
return 1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
if (!gladLoadGL()) {
fprintf(stderr, "[GFX] OpenGL load failed.\n");
return 1;
}
vy_fio_config fio_config = {0};
if (!vyInitFIO(&fio_config)) {
fprintf(stderr, "[FIO] Init failed.\n");
return 1;
}
if (!vyInitRenderer()) {
fprintf(stderr, "[GFX] Init failed.\n");
return 1;
}
printf("GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS: %u\n",
GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
printf("GL_MAX_UNIFORM_BUFFER_BINDINGS: %u\n",
GL_MAX_UNIFORM_BUFFER_BINDINGS);
key_states prev_keys = {0};
int window_state = 0;
while (!glfwWindowShouldClose(window)) {
/* Gather input */
key_states keys;
keys.f1 = glfwGetKey(window, GLFW_KEY_F1);
keys.esc = glfwGetKey(window, GLFW_KEY_ESCAPE);
if (keys.f1 && !prev_keys.f1) {
if (window_state)
make_window_windowed(window, 640, 480);
else
make_window_windowed_fullscreen(window,
glfwGetPrimaryMonitor());
window_state = !window_state;
}
if (keys.esc)
glfwSetWindowShouldClose(window, GLFW_TRUE);
int fbw, fbh;
glfwGetFramebufferSize(window, &fbw, &fbh);
glViewport(0, 0, fbw, fbh);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
prev_keys = keys;
}
vyShutdownRenderer();
vyShutdownFIO();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

12
subprojects/glfw.wrap Normal file
View File

@ -0,0 +1,12 @@
[wrap-file]
directory = glfw-3.3.8
source_url = https://github.com/glfw/glfw/archive/refs/tags/3.3.8.tar.gz
source_filename = glfw-3.3.8.tar.gz
source_hash = f30f42e05f11e5fc62483e513b0488d5bceeab7d9c5da0ffe2252ad81816c713
patch_filename = glfw_3.3.8-2_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/glfw_3.3.8-2/get_patch
patch_hash = eca865a15ff49d29d1a710fda3cfb9ca82057dda7f15ed617a1677cc5992c503
wrapdb_version = 3.3.8-2
[provide]
glfw3 = glfw_dep