azur: basic gint/SDL/emscripten setup with rendering

This basic model supports a couple of options, but mostly designed for
three targets:

* gint on fx-CG 50, with a custom XRAM-streaming rendering engine that
  is (to my knowledge) more powerful than all other existing options;

* The standard SDL/OpenGL combo for desktop platforms, using modern
  OpenGL 3.3 and some basic shaders.

* The web platform with emscripten and its OpenGL ES 2.0 API that maps
  to WebGL 1.0. OpenGL ES 2.0 code could also be used for mobile
  platforms if this ever comes up.

The current code only includes setting up the program's main loop
(update/render) as well as basic rendering.

The framework of gint's rendering engine is drawn ("shader"-based
rendering in XRAM). On the OpenGL side, some utilities are defined,
including a primitive layer of shader code compatibility for OpenGL 3.3
vs. OpenGL ES 2.0. Actual rendering is still at prototype stage.
This commit is contained in:
Lephe 2021-07-04 14:46:36 +02:00
commit 3a3f607303
19 changed files with 1296 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
# Build folders
/build-cg
/build-linux
/build-emscripten
# Editor files
*.sublime-*

53
azur/CMakeLists.txt Normal file
View File

@ -0,0 +1,53 @@
cmake_minimum_required(VERSION 3.15)
project(Azur VERSION 0.1 LANGUAGES CXX C ASM)
configure_file(include/azur/config.h.in include/azur/config.h)
set(SOURCES
src/log.c)
set(ASSETS)
# All flavours of OpenGL
if(AZUR_GRAPHICS_OPENGL_3_3 OR AZUR_GRAPHICS_OPENGL_ES_2_0)
set(AZUR_GRAPHICS_OPENGL TRUE)
endif()
# SDL/OpenGL rendering
if(AZUR_TOOLKIT_SDL AND AZUR_GRAPHICS_OPENGL)
list(APPEND SOURCES
src/sdl_opengl/init.c
src/sdl_opengl/util.c)
list(APPEND ASSETS
glsl/vs_prelude_gles2.glsl
glsl/fs_prelude_gles2.glsl
glsl/vs_tex2d.glsl
glsl/fs_tex2d.glsl)
endif()
# gint rendering
if(AZUR_GRAPHICS_GINT_CG)
list(APPEND SOURCES
src/gint/render.c
src/gint/shaders/tex2d.S)
endif()
add_library(azur STATIC ${SOURCES})
# File preloading on emscripten
if(AZUR_PLATFORM_EMSCRIPTEN)
set_target_properties(azur PROPERTIES
INTERFACE_LINK_OPTIONS "SHELL:--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/glsl@/azur/glsl"
LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/glsl")
# Add assets to link dependencies
set(ASSETS_ABSOLUTE)
foreach(ASSET IN LISTS ASSETS)
list(APPEND ASSETS_ABSOLUTE "${CMAKE_CURRENT_SOURCE_DIR}/${ASSET}")
endforeach()
set_target_properties(azur PROPERTIES LINK_DEPENDS "${ASSETS_ABSOLUTE}")
endif()
target_include_directories(azur
PUBLIC "${PROJECT_SOURCE_DIR}/include"
PUBLIC "${PROJECT_BINARY_DIR}/include"
)

View File

@ -0,0 +1,4 @@
#version 330 core
#define _GL(expr) expr
#define _GLES(expr)

View File

@ -0,0 +1,8 @@
#version 100
#define in varying
#define _GL(expr)
#define _GLES(expr) expr
precision mediump float;

13
azur/glsl/fs_tex2d.glsl Normal file
View File

@ -0,0 +1,13 @@
in vec2 v_position;
_GL(out vec4 color;)
uniform sampler2D u_image;
void main() {
#ifdef GL_ES
gl_FragColor = texture2D(u_image, v_position);
#else
color = texture(u_image, v_position);
#endif
}

View File

@ -0,0 +1,4 @@
#version 330 core
#define _GL(expr) expr
#define _GLES(expr)

View File

@ -0,0 +1,10 @@
#version 100
#define in attribute
#define out varying
#define layout(arg)
#define _GL(expr)
#define _GLES(expr) expr
precision mediump float;

21
azur/glsl/vs_tex2d.glsl Normal file
View File

@ -0,0 +1,21 @@
/* Vertex position in screen space */
layout(location=0) in vec2 a_vertex;
/* Same in texture space */
layout(location=1) in vec2 a_texture_pos;
/* Location in image space */
out vec2 v_position;
uniform vec3 u_windowSize;
void main() {
v_position = a_texture_pos;
float w = u_windowSize.x / 2.0;
float h = u_windowSize.y / 2.0;
gl_Position.x = (a_vertex.x - w) / w;
gl_Position.y = -(a_vertex.y - h) / h;
gl_Position.z = 0.0;
gl_Position.w = 1.0;
}

72
azur/include/azur/azur.h Normal file
View File

@ -0,0 +1,72 @@
//---
// azur.azur: Main functions
//---
#pragma once
#include <azur/defs.h>
AZUR_BEGIN_DECLS
/* azur_init(): Initialize the engine's subsystems.
Initializes either the SDL with OpenGL/OpenGLES, or gint. Returns 0 on
success, non-zero on failure. Resources allocated by azur_init() are
automatically destroyed by a destructor.
On GINT_CG, the window size is fixed to 396x224 and ignored.
TODO: On GINT_CG, accept super-resolution in azur_init(). */
int azur_init(int window_width, int window_height);
/* azur_main_loop(): Run the update/render loop.
This function runs the main loop, which regularly calls two different
functions for frame renders (usually redraws and commits the screen) and
application updates (usually reads SDL/gint events and runs simulations).
Renders and updates can either be triggered independently by timers with
different frequencies, or be triggered together. Additionally, frame renders
might be synchronized with display refresh.
-> When using standard SDL, both can be set independently, and vsync is
supported (if it succeeds at runtime, otherwise a fallback is used).
-> When using emscripten's ported SDL, update frequency can be set with a
timer, but frame renders are always synchronized with the browser's
refresh because it is the only sensible and supported option.
-> When using gint, both can be set independently; there is no vertical
synchronization but full-speed can be enabled (as it makes sense there).
The render framerate is determined first. If AZUR_MAIN_LOOP_FULLSPEED is
set, renders and updates alternate at full speed (gint only). Otherwise, if
AZUR_MAIN_LOOP_VSYNC is set (forced on emscripten), vsync is used. If vsync
fails, or neither flag was specified, the target FPS is used. Thus the
render FPS should always be specified.
The update framerate is determined second. If AZUR_MAIN_LOOP_TIED is set,
updates are set to run before renders (except before the very first render),
and update_ups is ignored is ignored. Otherwise, the target UPS is used.
The main loop stops whenever update() returns non-zero. */
int azur_main_loop(
void (*render)(void), int render_fps,
int (*update)(void), int update_ups,
int flags);
/* Render loop is synchronized with display refresh. */
#define AZUR_MAIN_LOOP_VSYNC 0x01
/* Both loops run at full speed with no delay (relevant on gint). */
#define AZUR_MAIN_LOOP_UNLIMITED 0x02
/* Update loop is tied to the render loop. */
#define AZUR_MAIN_LOOP_TIED 0x04
//---
// Global information
//---
#ifdef AZUR_TOOLKIT_SDL
#include <SDL2/SDL.h>
/* azur_sdl_window(): Get the current SDL window. */
SDL_Window *azur_sdl_window(void);
#endif /* AZUR_TOOLKIT_SDL */
AZUR_END_DECLS

View File

@ -0,0 +1,53 @@
//---
// azur.config: Compile-time configuration
//---
#pragma once
#define AZUR_VERSION_MAJOR @Azur_VERSION_MAJOR@
#define AZUR_VERSION_MINOR @Azur_VERSION_MINOR@
/* Whether this is a debug build (if not, release build obviously). */
#cmakedefine AZUR_DEBUG
//---
// In each of the following groups, options are mutually exclusive. Exactly one
// of each category is specified in a given build.
//---
/* Target platform. This only distinguishes platforms with major programming
differences. when possible, use tests on specific API requirements
rather than the platform (since platform doesn't tell that much). */
/* Web browser with emscripten. */
#cmakedefine AZUR_PLATFORM_EMSCRIPTEN
/* Generic platform. */
#cmakedefine AZUR_PLATFORM_GENERIC
/* Toolkit API; for windows, timers, audio, fonts, etc. */
/* SDL2. */
#cmakedefine AZUR_TOOLKIT_SDL
/* Native gint drivers on fx-CG. */
#cmakedefine AZUR_TOOLKIT_GINT
/* Graphics API for rendering tasks. */
/* OpenGL 3.3 core (essentially desktop targets). */
#cmakedefine AZUR_GRAPHICS_OPENGL_3_3
/* WebGL-friendly subset of OpenGL ES 2.0 (mobile targets and emscripten). */
#cmakedefine AZUR_GRAPHICS_OPENGL_ES_2_0
/* Custom broken rendering engine for the fx-CG running gint. */
#cmakedefine AZUR_GRAPHICS_GINT_CG
/* Input/output settings on terminal. */
/* No terminal output entirely. */
#cmakedefine AZUR_TERMINAL_NONE
/* No formatting or colors. */
#cmakedefine AZUR_TERMINAL_PLAIN
/* Formatting with ANSI escape codes. */
#cmakedefine AZUR_TERMINAL_ANSI

31
azur/include/azur/defs.h Normal file
View File

@ -0,0 +1,31 @@
//---
// azur.defs: Generation definitions
//---
/* This exposes compile-time configuration symbols. I don't like running the
risk of using preprocessor conditionals without pulling the configuration,
and by getting it here, every header will include it. */
#include <azur/config.h>
/* C++ header guards. */
#ifdef __cplusplus
# define AZUR_BEGIN_DECLS extern "C" {
# define AZUR_END_DECLS }
#else
# define AZUR_BEGIN_DECLS
# define AZUR_END_DECLS
#endif
/* Common types. */
#ifdef __cplusplus
# include <cstdint>
# include <cstddef>
#else
# include <inttypes.h>
# include <stddef.h>
# include <stdbool.h>
# include <stdarg.h>
#endif
/* More common types. */
#include <sys/types.h>

View File

@ -0,0 +1,183 @@
//---
// azur.render: Specialized rendering pipeline for fx-CG gint
//
// On-chip ILRAM and DSP memory bring out the full power of the SH4AL-DSP.
// Therefore, optimal performance in a game's renderer will rely on using on-
// chip memory instead of standard RAM for graphics data.
//
// The obvious limitation of on-chip memory is its size (20 kiB total), which
// is much smaller than a full-resolution image (~177 kiB). This prompts for a
// technique known as "frame streaming", where fragments (strips of VRAM) of
// each frame are rendered and transferred to the display in sequence.
// Reasonable efficiency and suitable display driver settings can prevent
// tearing even though data is not being sent continuously.
//
// The main components of this rendering pipeline are the command queue and
// fragment shaders.
//
// The command queue stores all rendering commands, split into fragments. Each
// fragment needs to read through all commands to produce its output, and
// because fragments are rendered in order, the sequence of commands must be
// read several times, therefore stored.
//
// Fragment shaders are the programs that render commands into graphics data
// for each fragments. They are pretty similar to OpenGL shaders, in that they
// receive vectors of objects (command parameters) and produce graphics data to
// fragments (even though each fragment is a strip of VRAM, not a pixel), hence
// the name.
//
// The prefix for this module is [azrp] for "azur rendering pipeline".
//---
#pragma once
#include <azur/defs.h>
AZUR_BEGIN_DECLS
#include <gint/defs/types.h>
#include <gint/display.h>
#include <libprof.h>
/* arzp_shader_t: Type of shader functions
* [uniforms] is a pointer to any data the shader might use as uniform.
* [command] is a structure of the shader's command type. */
typedef void azrp_shader_t(void *uniforms, void *command);
/* Video memory fragment used as rendering target (in XRAM). */
extern uint16_t azrp_frag[];
/* Maximum number of commands that can be queued. (This is only one of two
limits, the other being the size of the command data.) */
#define AZRP_MAX_COMMANDS 512
/* Maximum number of shaders that can be defined. (This is a loose limit). */
#define AZRP_MAX_SHADERS 32
//---
// High and low-level pipeline functions
//
// The process of rendering a frame with azrp has four steps:
// 1. Clear the command queue
// 2. Queue commands with command generation functions
// 3. Sort the command queue
// 4. Render fragments in on-chip memory and send them to the display driver
//
// The command queue is empty when the program starts. The azrp_update()
// performs steps 3 and 4, rendering a frame; then clears the command queue
// again. Therefore, azrp_update() can be used more or less like dupdate().
//
// Functions for command generation are listed in the shader API below, and can
// be extended with custom shaders.
//
// Applications that want to render the same frame several time such as to
// save screenshots, or reuse commands, or add new commands and use the
// already-sorted base, can use the low-level functions below which implement
// steps 1, 3 and 4 individually.
//---
/* azrp_update(): Sort commands, render a frame, and starts another one */
void azrp_update(void);
/* azrp_clear_commands(): Clear the command queue (step 1) */
void azrp_clear_commands(void);
/* azrp_sort_commands(): Sort the command queue (step 3) */
void azrp_sort_commands(void);
/* azrp_render_fragments(): Render and send fragments to the dislay (step 4) */
void azrp_render_fragments(void);
//---
// Standard shaders
//---
enum {
/* Clears the entire output with a single color */
AZRP_SHADER_CLEAR = 0,
/* Renders RGB565 textures/images */
AZRP_SHADER_TEX2D,
/* First user-attributable ID */
AZRP_SHADER_USER,
};
/* azrp_clear(): Clear output [ARZP_SHADER_CLEAR] */
void azrp_clear(uint16_t color);
/* azrp_image(): Queue image command [AZRP_SHADER_TEX2D] */
void azrp_image(int x, int y, uint16_t *pixels, int w, int h, int stride);
//---
// Performance indicators
//
// The following performance counters are run through by the rendering module
// in most stages of the rendering process. The module updates them but doesn't
// use them, so they are safe to write to and reset when they're not running.
//---
/* This counter runs during command generation and enqueue operations, usually
between azrp_begin_frame() and azrp_render_frame(). */
extern prof_t azrp_perf_cmdgen;
/* This counter runs during the command sorting step, which occurs at the start
of azrp_render_frame(). */
extern prof_t azrp_perf_sort;
/* This counter runs during shader executions in arzp_render_frame(). */
extern prof_t azrp_perf_shaders;
/* This counter runs during CPU transfers to the R61524 display. */
extern prof_t azrp_perf_r61524;
/* This counter runs during the whole azrp_frame_render() operation; it is the
sum of sort, shaders, r61524, plus some logic overhead. */
extern prof_t azrp_perf_render;
/* azrp_perf_clear(): Clear all performance counters
Generally you want to do this before azrp_frame_begin(). */
void azrp_perf_clear(void);
//---
// Definitions for custom shaders
//---
/* azrp_register_shader(): Register a new command type and its shader program
This function adds the specified shader program to the program array, and
returns the corresponding command type (which is AZRP_SHADER_USER plus some
value). Adding new shaders is useful for specialized rendering options (eg.
tiles with fixed size) or new graphical effects.
If the maximum number shaders is exceeded, returns -1. */
int azrp_register_shader(azrp_shader_t *program);
/* azrp_queue_command(): Add a new command to be rendered next frame
The command must be a structure starting with an 8-bit shader ID and an
8-bit fragment ID.
Returns true on success, false if the maximum amount of commands or command
memory is exceeded. */
bool azrp_queue_command(void *command, size_t size);
//---
// Internal shader definitions (for reference; no API guarantee)
//---
struct azrp_shader_tex2d_command {
/* Shader ID and fragment number */
uint8_t shader_id;
uint8_t fragment_id;
/* Pixels per line */
int16_t columns;
/* Already offset by start row and column */
void *input;
/* Destination in XRAM */
void *output;
/* Number of lines */
int16_t lines;
/* Distance between two lines (columns excluded) */
int16_t stride;
};
AZUR_END_DECLS

54
azur/include/azur/log.h Normal file
View File

@ -0,0 +1,54 @@
//---
// azur.log: Logging utilities
//---
#pragma once
#include <azur/defs.h>
AZUR_BEGIN_DECLS
/* Message levels, numbered by "severity". */
enum {
AZLOG_DEBUG = 0,
AZLOG_INFO = 1,
AZLOG_WARN = 2,
AZLOG_ERROR = 3,
AZLOG_FATAL = 4,
};
/* azlog_level(): Get the current logging level
Any message with a level at least the current logging level is printed. The
default logging level is AZLOG_ERROR in release builds, AZLOG_DEBUG in
development builds. */
int azlog_level(void);
/* azlog_set_level(): Set the current logging level */
void azlog_set_level(int level);
/* azlog(): Write an error message
This macro produces a line of log at the specified level. The AZLOG_ prefix
is added automatically by the macro, as well as standard function/line
information in development builds. */
#define azlog(level, fmt, ...) \
azlog_write(AZLOG_ ## level, __FILE__, __LINE__, __func__, false, fmt, \
## __VA_ARGS__)
/* azlogc(): Continue an error message
Same as azlog(), but inhibits prefixes, for multi-part messages. */
#define azlogc(level, fmt, ...) \
azlog_write(AZLOG_ ## level, __FILE__, __LINE__, __func__, true, fmt, \
## __VA_ARGS__)
/* Disable output in gint. */
#ifdef AZUR_TERMINAL_NONE
# undef azlog
# undef azlogc
# define azlog(level, fmt, ...)
# define azlogc(level, fmt, ...)
#endif
/* azlog_write(): Support function for azlog() */
void azlog_write(int level, char const *file, int line, char const *func,
bool cont, char const *fmt, ...);
AZUR_END_DECLS

View File

@ -0,0 +1,48 @@
//---
// azur.sdl_opengl.gl: General OpenGL utilities
//---
#pragma once
#include <azur/defs.h>
AZUR_BEGIN_DECLS
#define GL_GLEXT_PROTOTYPES
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_opengl_glext.h>
/* azgl_error(): String description of a GLenum error code.
The returned pointer might be a static buffer modified by further calls. */
char const *azgl_error(GLenum error_code);
/* azgl_compile_shader_file(): Load and compile a shader from file.
This function loads the file at [path], and compiles it into a shader of the
specified type. If the file cannot be loaded, the shader cannot be created,
or there are compilation errors, this function returns 0. Otherwise, it
returns the shader ID. */
GLuint azgl_compile_shader_file(GLenum type, char const *path);
/* azgl_compile_shader_source(): Load and compile a shader from its code.
Like azgl_load_shader(), but without the filesystem step. If the size is set
to -1, [code] is assumed to be NUL-terminated. */
GLuint azgl_compile_shader_source(GLenum type, char const *code, ssize_t size);
/* azgl_link_program(): Link a program.
This function attaches the 0-terminated list of shaders to a program, links
it, and returns the program ID (0 if link fails). */
GLuint azgl_link_program(GLuint shader_1, ... /* 0-terminated */);
/* azgl_load_program(): Load a program from shader source files.
This function runs both azgl_compile_shader_file() and azgl_link_program()
for all specified files. Each argument should be a pair with a shader type
and a file name, with a final 0.
Returns the program ID, or 0 if any loading/compilation/link step fails. */
GLuint azgl_load_program(
GLenum type_1, char const *path_1,
... /* Pairs repeat until 0-terminated */);
AZUR_END_DECLS

155
azur/src/gint/render.c Normal file
View File

@ -0,0 +1,155 @@
#include <azur/gint/render.h>
#include <gint/drivers/r61524.h>
#include <gint/defs/attributes.h>
#include <string.h>
#include <stdlib.h>
#define YRAM ((void *)0xe5017000)
/* 8 rows of video memory, occupying 6338/8192 bytes of XRAM. */
GXRAM GALIGNED(32) uint16_t azrp_frag[DWIDTH * 8];
/* Number and total size of queued commands. */
GXRAM int commands_count = 0, commands_length = 0;
/* Array of pointers to queued commands (stored as an offset into YRAM). */
GXRAM uint16_t commands_array[AZRP_MAX_COMMANDS];
/* Default shader programs. */
extern azrp_shader_t azrp_shader_tex2d;
/* Array of shader programs. */
GXRAM azrp_shader_t *shaders[AZRP_MAX_SHADERS] = {
[AZRP_SHADER_CLEAR] = NULL, /* TODO: Clear shader */
[AZRP_SHADER_TEX2D] = &azrp_shader_tex2d,
};
/* Next free index in the shader program array. */
GXRAM static uint16_t shaders_next = AZRP_SHADER_USER;
/* Performance counters. */
GXRAM prof_t azrp_perf_cmdgen;
GXRAM prof_t azrp_perf_sort;
GXRAM prof_t azrp_perf_shaders;
GXRAM prof_t azrp_perf_r61524;
GXRAM prof_t azrp_perf_render;
//---
// High and low-level pipeline functions
//---
void azrp_clear_commands(void)
{
commands_count = 0;
commands_length = 0;
}
static int compare_commands(void const *c1, void const *c2)
{
uint16_t offset1 = *(uint16_t *)c1;
uint16_t offset2 = *(uint16_t *)c2;
uint8_t *ptr1 = (uint8_t *)(0xe5017000 + offset1);
uint8_t *ptr2 = (uint8_t *)(0xe5017000 + offset2);
int diff_fragments = (int)ptr1[1] - (int)ptr2[1];
if(diff_fragments) return diff_fragments;
return (int)offset1 - (int)offset2;
}
void azrp_sort_commands(void)
{
prof_enter(azrp_perf_sort);
/* TODO: azrp_sort_commands: Use a custom sorter */
qsort(commands_array, commands_count, sizeof commands_array[0],
compare_commands);
prof_leave(azrp_perf_sort);
}
void azrp_render_fragments(void)
{
prof_enter(azrp_perf_render);
int i = 0;
int frag = 0;
uint8_t *cmd = (uint8_t *)YRAM + commands_array[i];
prof_enter(azrp_perf_r61524);
r61524_start_frame(0, 244);
prof_leave(azrp_perf_r61524);
while(1) {
if(cmd[1] == frag) {
if(shaders[cmd[0]]) {
prof_enter(azrp_perf_shaders);
shaders[cmd[0]](NULL, cmd);
prof_leave(azrp_perf_shaders);
}
cmd = YRAM + commands_array[++i];
}
else {
prof_enter(azrp_perf_r61524);
xram_frame(azrp_frag, 396 * 8);
prof_leave(azrp_perf_r61524);
frag++;
if(frag == 28) break;
}
}
prof_leave(azrp_perf_render);
}
void azrp_update(void)
{
azrp_sort_commands();
azrp_render_fragments();
azrp_clear_commands();
}
//---
// Custom shaders
//---
int azrp_register_shader(azrp_shader_t *program)
{
int id = shaders_next;
if(id >= AZRP_MAX_SHADERS)
return -1;
shaders[shaders_next++] = program;
return id;
}
bool azrp_queue_command(void *command, size_t size)
{
if(commands_count >= AZRP_MAX_COMMANDS)
return false;
if(commands_length + size >= 8192)
return false;
commands_array[commands_count++] = commands_length;
memcpy(YRAM + commands_length, command, size);
commands_length += size;
return true;
}
//---
// Performance indicators
//---
/* Also run this function once at startup */
GCONSTRUCTOR
void azrp_perf_clear(void)
{
azrp_perf_cmdgen = prof_make();
azrp_perf_sort = prof_make();
azrp_perf_shaders = prof_make();
azrp_perf_r61524 = prof_make();
azrp_perf_render = prof_make();
}

View File

@ -0,0 +1,60 @@
.global _azrp_shader_tex2d
.align 4
/* Register assignment
r1: Lines
r2: Columns
r3: Input
r4: Output
r5: Command queue
r7: Constant 396*2 = 0x318
r8: Output stride
r9: Input stride */
_azrp_shader_tex2d:
mov.l r8, @-r15
add #2, r5
mov.l r9, @-r15
mov #0x03, r7
ldrs 1f
shll8 r7
ldre 2f
add #0x18, r7
/* CHECK: 4-alignment here */
.texture:
mov.w @r5+, r2 /* Columns */
mov r7, r8
mov.l @r5+, r3 /* Input */
mov r2, r0
mov.l @r5+, r4 /* Output */
shll r0
mov.w @r5+, r1 /* Lines */
sub r0, r8
mov.w @r5+, r9 /* Input stride */
shlr r2
.line:
ldrc r2
dt r1
1: movs.l @r3+, x0
2: movs.l x0, @r4+
add r8, r4
bf.s .line
add r9, r3
.end:
mov.l @r15+, r9
rts
mov.l @r15+, r8

57
azur/src/log.c Normal file
View File

@ -0,0 +1,57 @@
#include <azur/log.h>
#include <stdio.h>
#ifdef AZUR_DEBUG
static int _level = AZLOG_DEBUG;
#else
static int _level = AZLOG_ERROR;
#endif
#ifdef AZUR_TERMINAL_ANSI
# define RED "\e[31m"
# define CYAN "\e[36m"
# define YELLOW "\e[33m"
# define STOP "\e[0m"
#else /* AZUR_TERMINAL_PLAIN, by default */
# define RED
# define CYAN
# define YELLOW
# define STOP
#endif
int azlog_level(void)
{
return _level;
}
void azlog_set_level(int level)
{
_level = level;
}
void azlog_write(int level, char const *file, int line, char const *func,
bool cont, char const *fmt, ...)
{
if(level < _level) return;
if(!cont) {
#ifdef AZUR_DEBUG
fprintf(stderr, CYAN "%s:%d: " STOP YELLOW "%s: " STOP,
file, line, func);
#else
fprintf(stderr, "%s: ", func);
#endif
if(level == AZLOG_ERROR)
fprintf(stderr, RED "error: " STOP);
if(level == AZLOG_FATAL)
fprintf(stderr, RED "fatal error: " STOP);
}
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}

235
azur/src/sdl_opengl/init.c Normal file
View File

@ -0,0 +1,235 @@
#include <azur/azur.h>
#include <azur/log.h>
#include <azur/sdl_opengl/gl.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
static SDL_Window *window = NULL;
static SDL_GLContext glcontext = NULL;
static void main_loop_quit(void);
int azur_init(int window_width, int window_height)
{
int rc = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
if(rc < 0) {
azlog(FATAL, "SDL_Init: %s\n", SDL_GetError());
return 1;
}
rc = IMG_Init(IMG_INIT_PNG);
if(rc != IMG_INIT_PNG) {
azlog(FATAL, "IMG_Init: %s\n", IMG_GetError());
return 1;
}
/* Select OpenGL 3.3 core, but not on the OpenGL ES/emscripten backend,
which does not support context attributes. */
#ifdef AZUR_GRAPHICS_OPENGL_3_3
SDL_GL_SetAttribute(
SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
#endif
window = SDL_CreateWindow("facets-floating",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
window_width, window_height,
SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if(!window) {
azlog(FATAL, "SDL_CreateWindow: %s\n", SDL_GetError());
return 1;
}
glcontext = SDL_GL_CreateContext(window);
if(!glcontext) {
azlog(FATAL, "SDL_GL_CreateContext: %s\n", SDL_GetError());
return 1;
}
rc = SDL_GL_SetSwapInterval(0);
if(rc < 0)
azlog(ERROR, "SDL_GL_SetSwapInterval: %s\n", SDL_GetError());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
return 0;
}
__attribute__((destructor))
void azur_quit(void)
{
main_loop_quit();
if(window) SDL_DestroyWindow(window);
if(glcontext) SDL_GL_DeleteContext(glcontext);
SDL_Quit();
window = NULL;
glcontext = NULL;
}
#ifdef AZUR_TOOLKIT_SDL
SDL_Window *azur_sdl_window(void)
{
return window;
}
#endif /* AZUR_TOOLKIT_SDL */
//---
// Main loop setup
//---
/* Time spent in the main loop (seconds) */
static double ml_time = 0.0;
/* In emscripten, callbacks are void/void, vsync is always ON, and the
mechanism used to generate updates is not a timer. */
#ifdef AZUR_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
#include <emscripten/html5.h>
struct params {
void (*render)(void);
int (*update)(void);
bool tied, started;
};
static EM_BOOL frame(double time, void *data)
{
struct params *params = data;
if(params->tied && params->started)
params->update();
params->render();
params->started = true;
return EM_TRUE;
}
int azur_main_loop(
void (*render)(void), int render_fps,
int (*update)(void), int update_ups,
int flags)
{
static struct params p;
p.render = render;
p.update = update;
p.tied = flags & AZUR_MAIN_LOOP_TIED;
p.started = false;
(void)render_fps;
emscripten_request_animation_frame_loop(frame, &p);
if(!(flags & AZUR_MAIN_LOOP_TIED)) {
void (*update_v)(void) = (void *)update;
emscripten_set_main_loop(update_v, update_ups, false);
}
return 0;
}
static void main_loop_quit(void)
{
}
/* In standard SDL, vsync is configurable and timers are used to get
callbacks. Events are queued to the main thread since rendering cannot be
done from another thread. */
#else
/* Event code for events queued to break SDL_WaitEvent() from timer handlers */
static int ml_event = -1;
/* Timers for render and updates */
static SDL_TimerID ml_timer_render = 0;
static SDL_TimerID ml_timer_update = 0;
static Uint32 handler(Uint32 interval, void *param)
{
*(int *)param = 1;
SDL_Event e = {
.user.type = SDL_USEREVENT,
.user.code = ml_event,
};
SDL_PushEvent(&e);
return interval;
}
int azur_main_loop(
void (*render)(void), int render_fps,
int (*update)(void), int update_ups,
int flags)
{
/* Register an event to wake up SDL_WaitEvent() on timer interrupts */
if(ml_event < 0) ml_event = SDL_RegisterEvents(1);
int mode = (flags & AZUR_MAIN_LOOP_VSYNC) ? 1 : 0;
int rc = SDL_GL_SetSwapInterval(mode);
if(rc < 0) {
azlog(ERROR, "SDL_GL_SetSwapInterval(%d): %s\n", mode, SDL_GetError());
azlog(ERROR, "Defaulting to non-vsync\n");
}
volatile int render_tick = 1;
volatile int update_tick = 0;
bool started = false;
if(mode == 0 || rc < 0) {
ml_timer_render = SDL_AddTimer(1000/render_fps, handler,
(void *)&render_tick);
}
if(!(flags & AZUR_MAIN_LOOP_TIED)) {
ml_timer_update = SDL_AddTimer(1000/update_ups, handler,
(void *)&update_tick);
}
while(1) {
if(update_tick && !(flags & AZUR_MAIN_LOOP_TIED)) {
update_tick = 0;
if(update()) break;
}
/* When vsync is enabled, render() is indirectly blocking so we just
call it in a straight loop. */
if(render_tick || (flags & AZUR_MAIN_LOOP_VSYNC)) {
render_tick = 0;
/* Tied renders and updates */
if(started && (flags & AZUR_MAIN_LOOP_TIED)) {
if(update()) break;
}
render();
started = true;
}
/* We wait for an event here. The render_tick and update_tick flags
control when we call the user functions. In addition to these flags,
user events with type ml_event are pushed to the event queue to
"break" SDL_WaitEvent() without us needing to read from the queue
(which would take events away from the user). */
SDL_WaitEvent(NULL);
}
return 0;
}
static void main_loop_quit(void)
{
if(ml_timer_render > 0) {
SDL_RemoveTimer(ml_timer_render);
ml_timer_render = 0;
}
if(ml_timer_update > 0) {
SDL_RemoveTimer(ml_timer_update);
ml_timer_update = 0;
}
}
#endif /* emscripten SDL vs. standard SDL */

228
azur/src/sdl_opengl/util.c Normal file
View File

@ -0,0 +1,228 @@
#include <azur/sdl_opengl/gl.h>
#include <azur/log.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
/* Read the full contents of a file into the heap. Returns an malloc'd pointer
on success, NULL if an error occurs. */
static char *load_file(char const *path, size_t *out_size)
{
char *contents = NULL;
long size = 0;
FILE *fp = fopen(path, "r");
if(!fp) goto load_file_end;
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
contents = malloc(size + 1);
if(!contents) goto load_file_end;
if(out_size) *out_size = size;
fread(contents, size, 1, fp);
contents[size] = 0;
load_file_end:
if(fp) fclose(fp);
return contents;
}
char const *azgl_error(GLenum ec)
{
static char str[32];
switch(ec) {
case GL_NO_ERROR:
return "Success";
case GL_INVALID_ENUM:
return "Invalid enum";
case GL_INVALID_VALUE:
return "Invalid value";
case GL_INVALID_OPERATION:
return "Invalid operation";
case GL_STACK_OVERFLOW:
return "Stack overflow";
case GL_STACK_UNDERFLOW:
return "Stack underflow";
case GL_OUT_OF_MEMORY:
return "Out of memory";
}
snprintf(str, 32, "<GLenum %#x>", ec);
return str;
}
/* Common code for the shader compiling functions. */
static GLuint azgl_compile_shader(GLenum type, char const *code, ssize_t size,
char const *origin)
{
/* Shader prelude; this gives shader version, some macro definitions and
some "selctors" which altogether give some degree of compatibility
between GLSL and GLSL ES. */
static char const *vs_prelude = NULL;
static char const *fs_prelude = NULL;
if(!vs_prelude) {
#if defined AZUR_GRAPHICS_OPENGL_ES_2_0
vs_prelude = load_file("azur/glsl/vs_prelude_gles2.glsl", NULL);
#elif defined AZUR_GRAPHICS_OPENGL_3_3
vs_prelude = load_file("azur/glsl/vs_prelude_gl3.glsl", NULL);
#endif
}
if(!fs_prelude) {
#if defined AZUR_GRAPHICS_OPENGL_ES_2_0
fs_prelude = load_file("azur/glsl/fs_prelude_gles2.glsl", NULL);
#elif defined AZUR_GRAPHICS_OPENGL_3_3
fs_prelude = load_file("azur/glsl/fs_prelude_gl3.glsl", NULL);
#endif
}
char const *prelude = NULL;
if(type == GL_VERTEX_SHADER)
prelude = vs_prelude;
else if(type == GL_FRAGMENT_SHADER)
prelude = fs_prelude;
else
prelude = "";
GLuint id = glCreateShader(type);
if(id == 0) {
azlog(ERROR, "glCreateShader failed\n");
return 0;
}
GLint rc = GL_FALSE;
GLsizei log_length = 0;
char const *string_array[] = { prelude, code };
GLint size_array[] = { strlen(prelude), size ? size : (int)strlen(code) };
azlog(INFO, "Compiling shader: %s\n", origin);
glShaderSource(id, 2, string_array, size_array);
glCompileShader(id);
glGetShaderiv(id, GL_COMPILE_STATUS, &rc);
if(rc == GL_FALSE)
azlog(ERROR, "compilation failed!\n");
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length);
if(log_length > 0) {
GLchar *log = malloc((log_length + 1) * sizeof *log);
glGetShaderInfoLog(id, log_length, &log_length, log);
if(log_length > 0) {
azlogc(ERROR, "%s", log);
if(log[log_length - 1] != '\n')
azlogc(ERROR, "\n");
}
free(log);
}
return id;
}
GLuint azgl_compile_shader_file(GLenum type, char const *path)
{
size_t size;
char *source = load_file(path, &size);
if(!source) {
azlog(ERROR, "Cannot read '%s': %s\n", path, strerror(errno));
return 0;
}
GLuint id = azgl_compile_shader(type, source, size, path);
free(source);
return id;
}
GLuint azgl_compiler_shader_source(GLenum type, char const *code, ssize_t size)
{
return azgl_compile_shader(type, code, size, "<inline>");
}
static GLuint azgl_link(GLuint *shaders, int count)
{
GLuint prog = glCreateProgram();
if(prog == 0) {
azlog(ERROR, "glCreateProgram failed\n");
return 0;
}
/* Attach all shaders */
for(int i = 0; i < count; i++)
glAttachShader(prog, shaders[i]);
GLint rc = GL_FALSE;
GLsizei log_length = 0;
azlog(INFO, "Linking program\n");
glLinkProgram(prog);
glGetProgramiv(prog, GL_LINK_STATUS, &rc);
if(rc == GL_FALSE)
azlog(ERROR, "link failed!\n");
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length);
if(log_length > 0) {
GLchar *log = malloc((log_length + 1) * sizeof *log);
glGetProgramInfoLog(prog, log_length, &log_length, log);
if(log_length > 0) {
azlogc(ERROR, "%s", log);
if(log[log_length - 1] != '\n')
azlogc(ERROR, "\n");
}
free(log);
}
/* Detach all shaders */
for(int i = 0; i < count; i++)
glDetachShader(prog, shaders[i]);
return prog;
}
GLuint azgl_link_program(GLuint shader_1, ... /* 0-terminated */)
{
va_list args;
va_start(args, shader_1);
GLuint shaders[32];
int count = 0;
do {
shaders[count++] = shader_1;
shader_1 = va_arg(args, GLuint);
}
while(count < 32 && shader_1 != 0);
va_end(args);
return azgl_link(shaders, count);
}
GLuint azgl_load_program(GLenum type, char const *path, ...)
{
va_list args;
va_start(args, path);
GLuint shaders[32];
int count = 0;
do {
shaders[count++] = azgl_compile_shader_file(type, path);
type = va_arg(args, GLenum);
path = va_arg(args, char const *);
}
while(count < 32 && type != 0);
va_end(args);
GLuint prog = azgl_link(shaders, count);
for(int i = 0; i < count; i++)
glDeleteShader(shaders[i]);
return prog;
}