Compare commits
19 Commits
c8a65f2894
...
double
Author | SHA1 | Date | |
---|---|---|---|
7e409fc14a | |||
aa3cab87d2 | |||
1dc0dea762 | |||
7f56ed8421 | |||
119b706638 | |||
f2bbc1863e | |||
768daf1f54 | |||
139394c6de | |||
446402c2e0 | |||
f7a718003e | |||
f087bf1f7f | |||
19bf88d44e | |||
3535a185df | |||
d34516c4ee | |||
b295c5920c | |||
f7f27119e1 | |||
ffab6a3924 | |||
82bad550e5 | |||
19b9812b3e |
@ -9,11 +9,11 @@ if(NOT EMSCRIPTEN)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified
|
# CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified
|
||||||
if(NOT CMAKE_BUILD_TYPE)
|
if(NOT DEFINED CMAKE_BUILD_TYPE)
|
||||||
set(CMAKE_BUILD_TYPE Debug)
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug)
|
if(NOT DEFINED TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug)
|
||||||
set(TWN_SANITIZE ON)
|
set(TWN_SANITIZE ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -104,6 +104,7 @@ set(TWN_NONOPT_SOURCE_FILES
|
|||||||
|
|
||||||
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
src/rendering/twn_draw.c src/rendering/twn_draw_c.h
|
||||||
src/rendering/twn_sprites.c
|
src/rendering/twn_sprites.c
|
||||||
|
src/rendering/twn_rects.c
|
||||||
src/rendering/twn_text.c
|
src/rendering/twn_text.c
|
||||||
src/rendering/twn_triangles.c
|
src/rendering/twn_triangles.c
|
||||||
src/rendering/twn_circles.c
|
src/rendering/twn_circles.c
|
||||||
@ -183,7 +184,7 @@ function(give_options_without_warnings target)
|
|||||||
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
||||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
|
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
|
||||||
-Bsymbolic-functions
|
-Bsymbolic-functions
|
||||||
-Wl,--hash-style=gnu)
|
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
|
||||||
|
|
||||||
target_compile_definitions(${target} PRIVATE
|
target_compile_definitions(${target} PRIVATE
|
||||||
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
|
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
|
||||||
|
@ -30,7 +30,9 @@ static void handle_input(void)
|
|||||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
||||||
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
||||||
state->bunnies[state->bunniesCount].color =
|
state->bunnies[state->bunniesCount].color =
|
||||||
(Color){(uint8_t)(rand() % 190 + 50), (uint8_t)(rand() % 160 + 80), (uint8_t)(rand() % 140 + 100), 255};
|
(Color){(uint8_t)(state->bunniesCount % 190 + 50),
|
||||||
|
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
|
||||||
|
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
|
||||||
state->bunniesCount++;
|
state->bunniesCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +48,9 @@ static void handle_input(void)
|
|||||||
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
|
||||||
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
|
||||||
state->bunnies[state->bunniesCount].color =
|
state->bunnies[state->bunniesCount].color =
|
||||||
(Color){(uint8_t)(rand() % 190 + 50), (uint8_t)(rand() % 160 + 80), (uint8_t)(rand() % 140 + 100), 255};
|
(Color){(uint8_t)(state->bunniesCount % 190 + 50),
|
||||||
|
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
|
||||||
|
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
|
||||||
state->bunniesCount++;
|
state->bunniesCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "twn_game_api.h"
|
#include "twn_game_api.h"
|
||||||
|
|
||||||
#define MAX_BUNNIES 100000 // 100K bunnies limit
|
#define MAX_BUNNIES 500000 // 100K bunnies limit
|
||||||
#define BUNNY_W 26
|
#define BUNNY_W 26
|
||||||
#define BUNNY_H 37
|
#define BUNNY_H 37
|
||||||
#define SPRITE_SCALE 1
|
#define SPRITE_SCALE 1
|
||||||
|
@ -20,9 +20,9 @@ static void title_tick(State *state) {
|
|||||||
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
|
||||||
|
|
||||||
/* draw the tick count as an example of dynamic text */
|
/* draw the tick count as an example of dynamic text */
|
||||||
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1;
|
size_t text_str_len = snprintf(NULL, 0, "%llu", state->ctx->frame_number) + 1;
|
||||||
char *text_str = cmalloc(text_str_len);
|
char *text_str = cmalloc(text_str_len);
|
||||||
snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number);
|
snprintf(text_str, text_str_len, "%llu", state->ctx->frame_number);
|
||||||
|
|
||||||
const char *font = "/fonts/kenney-pixel.ttf";
|
const char *font = "/fonts/kenney-pixel.ttf";
|
||||||
int text_h = 32;
|
int text_h = 32;
|
||||||
@ -39,7 +39,6 @@ static void title_tick(State *state) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
|
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
|
||||||
|
|
||||||
free(text_str);
|
free(text_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
bin/twn
2
bin/twn
@ -16,7 +16,7 @@ case "$1" in
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
gdb ) unset DEBUGINFOD_URLS
|
gdb ) unset DEBUGINFOD_URLS
|
||||||
$0 build && gdb -ex run --args "$(basename $PWD)" "${@:2}"
|
$0 build && gdb --se=libgame.so -ex run --args "$(basename $PWD)" "${@:2}"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
apitrace ) case "$2" in
|
apitrace ) case "$2" in
|
||||||
|
@ -22,6 +22,7 @@ TWN_API void draw_sprite(char const *path,
|
|||||||
TWN_API void draw_rectangle(Rect rect, Color color);
|
TWN_API void draw_rectangle(Rect rect, Color color);
|
||||||
|
|
||||||
/* pushes a filled circle onto the circle render queue */
|
/* pushes a filled circle onto the circle render queue */
|
||||||
|
/* note that its edges may look jagged with a radius larger than 2048 */
|
||||||
TWN_API void draw_circle(Vec2 position, float radius, Color color);
|
TWN_API void draw_circle(Vec2 position, float radius, Color color);
|
||||||
|
|
||||||
/* TODO: have font optional, with something minimal coming embedded */
|
/* TODO: have font optional, with something minimal coming embedded */
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#ifndef TWN_TEXTURES_MODES_H
|
#ifndef TWN_TEXTURES_MODES_H
|
||||||
#define TWN_TEXTURES_MODES_H
|
#define TWN_TEXTURES_MODES_H
|
||||||
|
|
||||||
|
/* TODO: rename, as it doesn't have to be about textures only, but blending */
|
||||||
|
/* TODO: move from public /include/ tree */
|
||||||
|
|
||||||
/* alpha channel information */
|
/* alpha channel information */
|
||||||
typedef enum TextureMode {
|
typedef enum TextureMode {
|
||||||
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
|
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
/* a point in some space (integer) */
|
/* a point in some space (integer) */
|
||||||
typedef struct Vec2i {
|
typedef struct Vec2i {
|
||||||
_Alignas(8)
|
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
} Vec2i;
|
} Vec2i;
|
||||||
@ -16,7 +15,6 @@ _Alignas(8)
|
|||||||
|
|
||||||
/* a point in some space (floating point) */
|
/* a point in some space (floating point) */
|
||||||
typedef struct Vec2 {
|
typedef struct Vec2 {
|
||||||
_Alignas(8)
|
|
||||||
float x;
|
float x;
|
||||||
float y;
|
float y;
|
||||||
} Vec2;
|
} Vec2;
|
||||||
@ -25,7 +23,6 @@ _Alignas(8)
|
|||||||
/* a point in some three dimension space (floating point) */
|
/* a point in some three dimension space (floating point) */
|
||||||
/* y goes up, x goes to the right */
|
/* y goes up, x goes to the right */
|
||||||
typedef struct Vec3 {
|
typedef struct Vec3 {
|
||||||
_Alignas(16)
|
|
||||||
float x;
|
float x;
|
||||||
float y;
|
float y;
|
||||||
float z;
|
float z;
|
||||||
@ -35,7 +32,6 @@ _Alignas(16)
|
|||||||
/* a point in some three dimension space (floating point) */
|
/* a point in some three dimension space (floating point) */
|
||||||
/* y goes up, x goes to the right */
|
/* y goes up, x goes to the right */
|
||||||
typedef struct Vec4 {
|
typedef struct Vec4 {
|
||||||
_Alignas(16)
|
|
||||||
float x;
|
float x;
|
||||||
float y;
|
float y;
|
||||||
float z;
|
float z;
|
||||||
@ -45,7 +41,6 @@ _Alignas(16)
|
|||||||
|
|
||||||
/* 32-bit color data */
|
/* 32-bit color data */
|
||||||
typedef struct Color {
|
typedef struct Color {
|
||||||
_Alignas(4)
|
|
||||||
uint8_t r;
|
uint8_t r;
|
||||||
uint8_t g;
|
uint8_t g;
|
||||||
uint8_t b;
|
uint8_t b;
|
||||||
@ -55,7 +50,6 @@ _Alignas(4)
|
|||||||
|
|
||||||
/* a rectangle with the origin at the upper left (integer) */
|
/* a rectangle with the origin at the upper left (integer) */
|
||||||
typedef struct Recti {
|
typedef struct Recti {
|
||||||
_Alignas(16)
|
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
int32_t w;
|
int32_t w;
|
||||||
@ -65,7 +59,6 @@ _Alignas(16)
|
|||||||
|
|
||||||
/* a rectangle with the origin at the upper left (floating point) */
|
/* a rectangle with the origin at the upper left (floating point) */
|
||||||
typedef struct Rect {
|
typedef struct Rect {
|
||||||
_Alignas(16)
|
|
||||||
float x;
|
float x;
|
||||||
float y;
|
float y;
|
||||||
float w;
|
float w;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#include "twn_util.h"
|
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_draw_c.h"
|
#include "twn_draw_c.h"
|
||||||
#include "twn_draw.h"
|
#include "twn_draw.h"
|
||||||
@ -22,28 +21,17 @@ void draw_circle(Vec2 position, float radius, Color color) {
|
|||||||
arrput(ctx.render_queue_2d, primitive);
|
arrput(ctx.render_queue_2d, primitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: caching and reuse scheme */
|
|
||||||
/* vertices_out and indices_out MUST BE FREED */
|
|
||||||
void create_circle_geometry(Vec2 position,
|
void create_circle_geometry(Vec2 position,
|
||||||
Color color,
|
|
||||||
float radius,
|
float radius,
|
||||||
size_t num_vertices,
|
size_t num_vertices,
|
||||||
SDL_Vertex **vertices_out,
|
Vec2 vertices[])
|
||||||
int **indices_out)
|
|
||||||
{
|
{
|
||||||
SDL_Vertex *vertices = cmalloc(sizeof *vertices * (num_vertices + 1));
|
|
||||||
int *indices = cmalloc(sizeof *indices * (num_vertices * 3));
|
|
||||||
|
|
||||||
/* the angle (in radians) to rotate by on each iteration */
|
/* the angle (in radians) to rotate by on each iteration */
|
||||||
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
|
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
|
||||||
|
|
||||||
vertices[0].position.x = (float)position.x;
|
vertices[0].x = (float)position.x;
|
||||||
vertices[0].position.y = (float)position.y;
|
vertices[0].y = (float)position.y;
|
||||||
vertices[0].color.r = color.r;
|
|
||||||
vertices[0].color.g = color.g;
|
|
||||||
vertices[0].color.b = color.b;
|
|
||||||
vertices[0].color.a = color.a;
|
|
||||||
vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
|
|
||||||
|
|
||||||
/* this point will rotate around the center */
|
/* this point will rotate around the center */
|
||||||
float start_x = 0.0f - radius;
|
float start_x = 0.0f - radius;
|
||||||
@ -52,37 +40,13 @@ void create_circle_geometry(Vec2 position,
|
|||||||
for (size_t i = 1; i < num_vertices + 1; ++i) {
|
for (size_t i = 1; i < num_vertices + 1; ++i) {
|
||||||
float final_seg_rotation_angle = (float)i * seg_rotation_angle;
|
float final_seg_rotation_angle = (float)i * seg_rotation_angle;
|
||||||
|
|
||||||
vertices[i].position.x =
|
float c, s;
|
||||||
cosf(final_seg_rotation_angle) * start_x -
|
sincosf(final_seg_rotation_angle, &s, &c);
|
||||||
sinf(final_seg_rotation_angle) * start_y;
|
|
||||||
vertices[i].position.y =
|
|
||||||
cosf(final_seg_rotation_angle) * start_y +
|
|
||||||
sinf(final_seg_rotation_angle) * start_x;
|
|
||||||
|
|
||||||
vertices[i].position.x += position.x;
|
vertices[i].x = c * start_x - s * start_y;
|
||||||
vertices[i].position.y += position.y;
|
vertices[i].y = c * start_y + s * start_x;
|
||||||
|
|
||||||
vertices[i].color.r = color.r;
|
vertices[i].x += position.x;
|
||||||
vertices[i].color.g = color.g;
|
vertices[i].y += position.y;
|
||||||
vertices[i].color.b = color.b;
|
|
||||||
vertices[i].color.a = color.a;
|
|
||||||
|
|
||||||
vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
|
|
||||||
|
|
||||||
|
|
||||||
size_t triangle_offset = 3 * (i - 1);
|
|
||||||
|
|
||||||
/* center point index */
|
|
||||||
indices[triangle_offset] = 0;
|
|
||||||
/* generated point index */
|
|
||||||
indices[triangle_offset + 1] = (int)i;
|
|
||||||
|
|
||||||
size_t index = (i + 1) % num_vertices;
|
|
||||||
if (index == 0)
|
|
||||||
index = num_vertices;
|
|
||||||
indices[triangle_offset + 2] = (int)index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*vertices_out = vertices;
|
|
||||||
*indices_out = indices;
|
|
||||||
}
|
}
|
||||||
|
@ -35,22 +35,6 @@ void render_queue_clear(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* rectangle */
|
|
||||||
void draw_rectangle(Rect rect, Color color) {
|
|
||||||
RectPrimitive rectangle = {
|
|
||||||
.rect = rect,
|
|
||||||
.color = color,
|
|
||||||
};
|
|
||||||
|
|
||||||
Primitive2D primitive = {
|
|
||||||
.type = PRIMITIVE_2D_RECT,
|
|
||||||
.rect = rectangle,
|
|
||||||
};
|
|
||||||
|
|
||||||
arrput(ctx.render_queue_2d, primitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
|
void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
|
||||||
const float bt = (float)border_thickness; /* i know! */
|
const float bt = (float)border_thickness; /* i know! */
|
||||||
const float bt2 = bt * 2; /* combined size of the two borders in an axis */
|
const float bt2 = bt * 2; /* combined size of the two borders in an axis */
|
||||||
@ -197,36 +181,152 @@ static void render_2d(void) {
|
|||||||
|
|
||||||
const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
|
const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
|
||||||
|
|
||||||
size_t batch_count = 0;
|
struct Render2DInvocation {
|
||||||
|
Primitive2D const *primitive;
|
||||||
|
double layer;
|
||||||
|
union {
|
||||||
|
struct QuadBatch quad_batch;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* first, collect all invocations, while merging into batches where applicable */
|
||||||
|
/* we separate into opaque and transparent ones, as it presents optimization opportunities */
|
||||||
|
struct Render2DInvocation *opaque_invocations = NULL;
|
||||||
|
struct Render2DInvocation *ghostly_invocations = NULL;
|
||||||
|
|
||||||
|
arrsetcap(opaque_invocations, render_queue_len);
|
||||||
|
arrsetcap(ghostly_invocations, render_queue_len);
|
||||||
|
|
||||||
for (size_t i = 0; i < render_queue_len; ++i) {
|
for (size_t i = 0; i < render_queue_len; ++i) {
|
||||||
const Primitive2D *current = &ctx.render_queue_2d[i];
|
const Primitive2D *current = &ctx.render_queue_2d[i];
|
||||||
|
|
||||||
|
// TODO: https://gamedev.stackexchange.com/questions/101136/using-full-resolution-of-depth-buffer-for-2d-rendering
|
||||||
|
double const layer = ((double)((render_queue_len + 1) - i) / (double)(render_queue_len + 1)) * 0.75;
|
||||||
|
|
||||||
switch (current->type) {
|
switch (current->type) {
|
||||||
case PRIMITIVE_2D_SPRITE: {
|
case PRIMITIVE_2D_SPRITE: {
|
||||||
const struct SpriteBatch batch =
|
const struct QuadBatch batch =
|
||||||
collect_sprite_batch(current, render_queue_len - i);
|
collect_sprite_batch(current, render_queue_len - i);
|
||||||
|
|
||||||
/* TODO: what's even the point? just use OR_EQUAL comparison */
|
struct Render2DInvocation const invocation = {
|
||||||
set_depth_range((double)batch_count / UINT16_MAX, 1.0);
|
.primitive = current,
|
||||||
render_sprites(current, batch);
|
.quad_batch = batch,
|
||||||
|
.layer = layer,
|
||||||
|
};
|
||||||
|
|
||||||
i += batch.size - 1; ++batch_count;
|
if (batch.mode == TEXTURE_MODE_GHOSTLY)
|
||||||
|
arrput(ghostly_invocations, invocation);
|
||||||
|
else
|
||||||
|
arrput(opaque_invocations, invocation);
|
||||||
|
|
||||||
|
i += batch.size - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PRIMITIVE_2D_RECT:
|
|
||||||
render_rectangle(¤t->rect);
|
case PRIMITIVE_2D_RECT: {
|
||||||
|
const struct QuadBatch batch =
|
||||||
|
collect_rect_batch(current, render_queue_len - i);
|
||||||
|
|
||||||
|
struct Render2DInvocation const invocation = {
|
||||||
|
.primitive = current,
|
||||||
|
.quad_batch = batch,
|
||||||
|
.layer = layer,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (batch.mode == TEXTURE_MODE_GHOSTLY)
|
||||||
|
arrput(ghostly_invocations, invocation);
|
||||||
|
else
|
||||||
|
arrput(opaque_invocations, invocation);
|
||||||
|
|
||||||
|
i += batch.size - 1;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PRIMITIVE_2D_CIRCLE: {
|
||||||
|
struct Render2DInvocation const invocation = {
|
||||||
|
.primitive = current,
|
||||||
|
.layer = layer,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (current->circle.color.a != 255)
|
||||||
|
arrput(ghostly_invocations, invocation);
|
||||||
|
else
|
||||||
|
arrput(opaque_invocations, invocation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case PRIMITIVE_2D_TEXT: {
|
||||||
|
struct Render2DInvocation const invocation = {
|
||||||
|
.primitive = current,
|
||||||
|
.layer = layer,
|
||||||
|
};
|
||||||
|
arrput(ghostly_invocations, invocation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* first issue all opaque primitives, front-to-back */
|
||||||
|
for (size_t i = 0; i < arrlenu(opaque_invocations); ++i) {
|
||||||
|
struct Render2DInvocation const invocation = opaque_invocations[arrlenu(opaque_invocations) - 1 - i];
|
||||||
|
|
||||||
|
/* idea here is to set constant z write that moves further and further along */
|
||||||
|
/* with that every batch can early z reject against the previous */
|
||||||
|
/* additionally, it will also apply for future transparent passes, sandwitching in-between */
|
||||||
|
set_depth_range(invocation.layer, 1.0);
|
||||||
|
|
||||||
|
switch (invocation.primitive->type) {
|
||||||
|
case PRIMITIVE_2D_SPRITE: {
|
||||||
|
render_sprite_batch(invocation.primitive, invocation.quad_batch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PRIMITIVE_2D_RECT: {
|
||||||
|
render_rect_batch(invocation.primitive, invocation.quad_batch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* TODO: circle batching */
|
||||||
case PRIMITIVE_2D_CIRCLE:
|
case PRIMITIVE_2D_CIRCLE:
|
||||||
render_circle(¤t->circle);
|
render_circle(&invocation.primitive->circle);
|
||||||
break;
|
break;
|
||||||
case PRIMITIVE_2D_TEXT:
|
case PRIMITIVE_2D_TEXT:
|
||||||
render_text(¤t->text);
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* then issue all transparent primitives, back-to-front */
|
||||||
|
for (size_t i = 0; i < arrlenu(ghostly_invocations); ++i) {
|
||||||
|
struct Render2DInvocation const invocation = ghostly_invocations[i];
|
||||||
|
|
||||||
|
/* now we use it not for writing layers, but inferring ordering */
|
||||||
|
set_depth_range(invocation.layer, 1.0);
|
||||||
|
|
||||||
|
switch (invocation.primitive->type) {
|
||||||
|
case PRIMITIVE_2D_SPRITE: {
|
||||||
|
render_sprite_batch(invocation.primitive, invocation.quad_batch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PRIMITIVE_2D_RECT: {
|
||||||
|
render_rect_batch(invocation.primitive, invocation.quad_batch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* TODO: circle batching */
|
||||||
|
case PRIMITIVE_2D_CIRCLE:
|
||||||
|
render_circle(&invocation.primitive->circle);
|
||||||
|
break;
|
||||||
|
case PRIMITIVE_2D_TEXT:
|
||||||
|
render_text(&invocation.primitive->text);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
SDL_assert(false);
|
SDL_assert(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrfree(opaque_invocations);
|
||||||
|
arrfree(ghostly_invocations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -274,11 +374,11 @@ void render(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render_space();
|
start_render_frame(); {
|
||||||
render_skybox(); /* after space, as to use depth buffer for early rejection */
|
render_space();
|
||||||
render_2d();
|
render_skybox(); /* after space, as to use depth buffer for early z rejection */
|
||||||
swap_buffers();
|
render_2d();
|
||||||
clear_draw_buffer();
|
} end_render_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,10 @@
|
|||||||
extern Matrix4 camera_projection_matrix;
|
extern Matrix4 camera_projection_matrix;
|
||||||
extern Matrix4 camera_look_at_matrix;
|
extern Matrix4 camera_look_at_matrix;
|
||||||
|
|
||||||
|
|
||||||
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
|
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
|
||||||
|
#define CIRCLE_VERTICES_MAX 2048
|
||||||
|
#define CIRCLE_ELEMENT_BUFFER_LENGTH (CIRCLE_VERTICES_MAX * 3)
|
||||||
|
|
||||||
|
|
||||||
typedef GLuint VertexBuffer;
|
typedef GLuint VertexBuffer;
|
||||||
@ -126,22 +129,25 @@ void render(void);
|
|||||||
/* clears all render queues */
|
/* clears all render queues */
|
||||||
void render_queue_clear(void);
|
void render_queue_clear(void);
|
||||||
|
|
||||||
|
/* fills two existing arrays with the geometry data of a circle */
|
||||||
|
/* the size of indices must be at least 3 times the number of vertices */
|
||||||
void create_circle_geometry(Vec2 position,
|
void create_circle_geometry(Vec2 position,
|
||||||
Color color,
|
|
||||||
float radius,
|
float radius,
|
||||||
size_t num_vertices,
|
size_t num_vertices,
|
||||||
SDL_Vertex **vertices_out,
|
Vec2 vertices[]);
|
||||||
int **indices_out);
|
|
||||||
|
|
||||||
struct SpriteBatch {
|
struct QuadBatch {
|
||||||
size_t size; /* how many primitives are in current batch */
|
size_t size; /* how many primitives are in current batch */
|
||||||
TextureMode mode;
|
TextureMode mode; /* how color should be applied */
|
||||||
bool constant_colored; /* whether colored batch is uniformly colored */
|
bool constant_colored; /* whether colored batch is uniformly colored */
|
||||||
bool repeat; /* whether repeat is needed */
|
bool repeat; /* whether repeat is needed */
|
||||||
} collect_sprite_batch(const Primitive2D primitives[], size_t len);
|
bool textured;
|
||||||
|
} collect_quad_batch(const Primitive2D primitives[], size_t len);
|
||||||
void render_sprites(const Primitive2D primitives[],
|
void render_quad_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
||||||
const struct SpriteBatch batch);
|
struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len);
|
||||||
|
struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len);
|
||||||
|
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
||||||
|
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
|
||||||
|
|
||||||
void draw_uncolored_space_traingle_batch(MeshBatch *batch,
|
void draw_uncolored_space_traingle_batch(MeshBatch *batch,
|
||||||
TextureKey texture_key);
|
TextureKey texture_key);
|
||||||
@ -160,16 +166,18 @@ void text_cache_reset_arena(TextCache *cache);
|
|||||||
|
|
||||||
VertexBuffer create_vertex_buffer(void);
|
VertexBuffer create_vertex_buffer(void);
|
||||||
|
|
||||||
|
VertexBuffer get_scratch_vertex_array(void);
|
||||||
|
|
||||||
void delete_vertex_buffer(VertexBuffer buffer);
|
void delete_vertex_buffer(VertexBuffer buffer);
|
||||||
|
|
||||||
void specify_vertex_buffer(VertexBuffer buffer, void *data, size_t bytes);
|
void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
|
||||||
|
|
||||||
/* uses present in 1.5 buffer mapping feature */
|
/* uses present in 1.5 buffer mapping feature */
|
||||||
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
|
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
|
||||||
|
|
||||||
/* collects bytes for sending to the gpu until all is pushed, which is when false is returned */
|
/* collects bytes for sending to the gpu until all is pushed, which is when false is returned */
|
||||||
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
||||||
void *bytes,
|
void const *bytes,
|
||||||
size_t size);
|
size_t size);
|
||||||
|
|
||||||
/* state */
|
/* state */
|
||||||
@ -182,7 +190,9 @@ void swap_buffers(void);
|
|||||||
|
|
||||||
void set_depth_range(double low, double high);
|
void set_depth_range(double low, double high);
|
||||||
|
|
||||||
void bind_quad_element_buffer(void);
|
VertexBuffer get_quad_element_buffer(void);
|
||||||
|
|
||||||
|
VertexBuffer get_circle_element_buffer(void);
|
||||||
|
|
||||||
void render_circle(const CirclePrimitive *circle);
|
void render_circle(const CirclePrimitive *circle);
|
||||||
|
|
||||||
@ -194,19 +204,17 @@ void use_2d_pipeline(void);
|
|||||||
|
|
||||||
void use_texture_mode(TextureMode mode);
|
void use_texture_mode(TextureMode mode);
|
||||||
|
|
||||||
void upload_quad_vertices(Rect rect);
|
void finally_render_quads(Primitive2D const primitives[],
|
||||||
|
struct QuadBatch batch,
|
||||||
|
VertexBuffer buffer);
|
||||||
|
|
||||||
void finally_render_sprites(Primitive2D const primitives[],
|
size_t get_quad_payload_size(struct QuadBatch batch);
|
||||||
struct SpriteBatch batch,
|
|
||||||
VertexBuffer buffer);
|
|
||||||
|
|
||||||
size_t get_sprite_payload_size(struct SpriteBatch batch);
|
bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
||||||
|
VertexBufferBuilder *builder,
|
||||||
bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
|
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
||||||
VertexBufferBuilder *builder,
|
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||||
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
Color color);
|
||||||
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
|
||||||
Color color);
|
|
||||||
|
|
||||||
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
|
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
|
||||||
TextureKey texture_key,
|
TextureKey texture_key,
|
||||||
@ -235,4 +243,8 @@ void pop_fog(void);
|
|||||||
|
|
||||||
void finally_pop_fog(void);
|
void finally_pop_fog(void);
|
||||||
|
|
||||||
|
void start_render_frame(void);
|
||||||
|
|
||||||
|
void end_render_frame(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
#include "twn_util_c.h"
|
#include "twn_util_c.h"
|
||||||
#include "twn_engine_context_c.h"
|
#include "twn_engine_context_c.h"
|
||||||
#include "twn_text_c.h"
|
#include "twn_text_c.h"
|
||||||
|
#include "twn_types.h"
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */
|
||||||
|
/* this is only doable is we take out color attribute to separate array or a portion of it */
|
||||||
/* interleaved vertex array data */
|
/* interleaved vertex array data */
|
||||||
/* TODO: use int16_t for uvs */
|
|
||||||
/* TODO: use packed types? */
|
|
||||||
/* TODO: int16_t could be used for positioning, but we would need to have more CPU calcs */
|
|
||||||
typedef struct ElementIndexedQuad {
|
typedef struct ElementIndexedQuad {
|
||||||
/* upper-left */
|
/* upper-left */
|
||||||
Vec2 v0;
|
Vec2 v0;
|
||||||
@ -31,7 +31,6 @@ typedef struct ElementIndexedQuad {
|
|||||||
Color c3;
|
Color c3;
|
||||||
} ElementIndexedQuad;
|
} ElementIndexedQuad;
|
||||||
|
|
||||||
|
|
||||||
typedef struct ElementIndexedQuadWithoutColor {
|
typedef struct ElementIndexedQuadWithoutColor {
|
||||||
/* upper-left */
|
/* upper-left */
|
||||||
Vec2 v0;
|
Vec2 v0;
|
||||||
@ -48,6 +47,75 @@ typedef struct ElementIndexedQuadWithoutColor {
|
|||||||
} ElementIndexedQuadWithoutColor;
|
} ElementIndexedQuadWithoutColor;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct ElementIndexedQuadWithoutTexture {
|
||||||
|
/* upper-left */
|
||||||
|
Vec2 v0;
|
||||||
|
Color c0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec2 v1;
|
||||||
|
Color c1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec2 v2;
|
||||||
|
Color c2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec2 v3;
|
||||||
|
Color c3;
|
||||||
|
} ElementIndexedQuadWithoutTexture;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
|
||||||
|
/* upper-left */
|
||||||
|
Vec2 v0;
|
||||||
|
/* bottom-left */
|
||||||
|
Vec2 v1;
|
||||||
|
/* bottom-right */
|
||||||
|
Vec2 v2;
|
||||||
|
/* upper-right */
|
||||||
|
Vec2 v3;
|
||||||
|
} ElementIndexedQuadWithoutColorWithoutTexture;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t offset;
|
||||||
|
GLenum type;
|
||||||
|
GLsizei stride;
|
||||||
|
GLuint buffer;
|
||||||
|
uint8_t arity; /* leave at 0 to signal pointer as unused */
|
||||||
|
} AttributeArrayPointer;
|
||||||
|
|
||||||
|
|
||||||
|
/* allows us to have generic way to issue draws as well as */
|
||||||
|
/* deferring new draw calls while previous frame is still being drawn */
|
||||||
|
typedef struct {
|
||||||
|
AttributeArrayPointer vertices;
|
||||||
|
AttributeArrayPointer texcoords;
|
||||||
|
|
||||||
|
bool constant_colored;
|
||||||
|
union {
|
||||||
|
AttributeArrayPointer colors;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool textured, texture_repeat, uses_gpu_key;
|
||||||
|
TextureKey texture_key;
|
||||||
|
GPUTexture gpu_texture;
|
||||||
|
|
||||||
|
GLuint element_buffer;
|
||||||
|
GLsizei element_count;
|
||||||
|
GLsizei range_start, range_end;
|
||||||
|
|
||||||
|
double depth_range_low, depth_range_high;
|
||||||
|
} DeferredCommandDraw;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Color color;
|
||||||
|
bool clear_color;
|
||||||
|
bool clear_depth;
|
||||||
|
bool clear_stencil;
|
||||||
|
} DeferredCommandClear;
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PIPELINE_NO,
|
PIPELINE_NO,
|
||||||
PIPELINE_SPACE,
|
PIPELINE_SPACE,
|
||||||
@ -55,10 +123,266 @@ typedef enum {
|
|||||||
} Pipeline;
|
} Pipeline;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Pipeline pipeline;
|
||||||
|
} DeferredCommandUsePipeline;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TextureMode mode;
|
||||||
|
} DeferredCommandUseTextureMode;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double low, high;
|
||||||
|
} DeferredCommandDepthRange;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
enum DeferredCommandType {
|
||||||
|
DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
DEFERRED_COMMAND_TYPE_CLEAR,
|
||||||
|
DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
|
||||||
|
DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
|
||||||
|
DEFERRED_COMMAND_TYPE_DEPTH_RANGE,
|
||||||
|
} type;
|
||||||
|
|
||||||
|
union {
|
||||||
|
DeferredCommandDraw draw;
|
||||||
|
DeferredCommandClear clear;
|
||||||
|
DeferredCommandUsePipeline use_pipeline;
|
||||||
|
DeferredCommandUseTextureMode use_texture_mode;
|
||||||
|
DeferredCommandDepthRange depth_range;
|
||||||
|
};
|
||||||
|
} DeferredCommand;
|
||||||
|
|
||||||
|
|
||||||
|
static TextureMode texture_mode_last_used = -1;
|
||||||
static Pipeline pipeline_last_used = PIPELINE_NO;
|
static Pipeline pipeline_last_used = PIPELINE_NO;
|
||||||
|
|
||||||
|
|
||||||
|
/* potentially double buffered array of vertex array handles */
|
||||||
|
/* we assume they will be refilled fully each frame */
|
||||||
|
static size_t scratch_va_front_used, scratch_va_back_used;
|
||||||
|
static GLuint *front_scratch_vertex_arrays, *back_scratch_vertex_arrays;
|
||||||
|
static GLuint **current_scratch_vertex_array = &front_scratch_vertex_arrays;
|
||||||
|
|
||||||
|
static void restart_scratch_vertex_arrays(void) {
|
||||||
|
scratch_va_front_used = 0;
|
||||||
|
scratch_va_back_used = 0;
|
||||||
|
|
||||||
|
if (ctx.render_double_buffered) {
|
||||||
|
current_scratch_vertex_array = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
|
||||||
|
&back_scratch_vertex_arrays : &front_scratch_vertex_arrays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GLuint get_scratch_vertex_array(void) {
|
||||||
|
size_t *used = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
|
||||||
|
&scratch_va_front_used : &scratch_va_back_used;
|
||||||
|
|
||||||
|
if (arrlenu(*current_scratch_vertex_array) <= *used) {
|
||||||
|
GLuint handle;
|
||||||
|
glGenBuffers(1, &handle);
|
||||||
|
arrpush(*current_scratch_vertex_array, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
(*used)++;
|
||||||
|
return (*current_scratch_vertex_array)[*used - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void finally_use_2d_pipeline(void);
|
||||||
|
static void finally_use_space_pipeline(void);
|
||||||
|
static void finally_use_texture_mode(TextureMode mode);
|
||||||
|
|
||||||
|
static DeferredCommand *deferred_commands;
|
||||||
|
|
||||||
|
static void issue_deferred_draw_commands(void) {
|
||||||
|
for (size_t i = 0; i < arrlenu(deferred_commands); ++i) {
|
||||||
|
switch (deferred_commands[i].type) {
|
||||||
|
case DEFERRED_COMMAND_TYPE_DEPTH_RANGE: {
|
||||||
|
glDepthRange(deferred_commands[i].depth_range.low, deferred_commands[i].depth_range.high);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DEFERRED_COMMAND_TYPE_CLEAR: {
|
||||||
|
glClearColor((1.0f / 255) * deferred_commands[i].clear.color.r,
|
||||||
|
(1.0f / 255) * deferred_commands[i].clear.color.g,
|
||||||
|
(1.0f / 255) * deferred_commands[i].clear.color.b,
|
||||||
|
(1.0f / 255) * deferred_commands[i].clear.color.a);
|
||||||
|
|
||||||
|
/* needed as we might mess with it */
|
||||||
|
glDepthRange(0.0, 1.0);
|
||||||
|
glDepthMask(GL_TRUE);
|
||||||
|
|
||||||
|
glClear((deferred_commands[i].clear.clear_color ? GL_COLOR_BUFFER_BIT : 0) |
|
||||||
|
(deferred_commands[i].clear.clear_depth ? GL_DEPTH_BUFFER_BIT : 0) |
|
||||||
|
(deferred_commands[i].clear.clear_stencil ? GL_STENCIL_BUFFER_BIT : 0) );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEFERRED_COMMAND_TYPE_DRAW: {
|
||||||
|
DeferredCommandDraw const command = deferred_commands[i].draw;
|
||||||
|
|
||||||
|
/* TODO: don't assume a single vertex array ? */
|
||||||
|
SDL_assert(command.vertices.arity != 0);
|
||||||
|
SDL_assert(command.vertices.buffer);
|
||||||
|
SDL_assert(command.element_count != 0);
|
||||||
|
SDL_assert(command.element_buffer);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, command.element_buffer);
|
||||||
|
|
||||||
|
glEnableClientState(GL_VERTEX_ARRAY);
|
||||||
|
glVertexPointer(command.vertices.arity,
|
||||||
|
command.vertices.type,
|
||||||
|
command.vertices.stride,
|
||||||
|
(void *)command.vertices.offset);
|
||||||
|
|
||||||
|
if (command.texcoords.arity != 0) {
|
||||||
|
SDL_assert(command.texcoords.buffer == command.vertices.buffer);
|
||||||
|
|
||||||
|
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||||
|
glClientActiveTexture(GL_TEXTURE0);
|
||||||
|
glTexCoordPointer(command.texcoords.arity,
|
||||||
|
command.texcoords.type,
|
||||||
|
command.texcoords.stride,
|
||||||
|
(void *)command.texcoords.offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.colors.arity != 0) {
|
||||||
|
SDL_assert(command.colors.buffer == command.vertices.buffer);
|
||||||
|
|
||||||
|
glEnableClientState(GL_COLOR_ARRAY);
|
||||||
|
glColorPointer(command.colors.arity,
|
||||||
|
command.colors.type,
|
||||||
|
command.colors.stride,
|
||||||
|
(void *)command.colors.offset);
|
||||||
|
} else if (command.constant_colored)
|
||||||
|
glColor4ub(command.color.r,
|
||||||
|
command.color.g,
|
||||||
|
command.color.b,
|
||||||
|
command.color.a);
|
||||||
|
|
||||||
|
if (command.textured) {
|
||||||
|
if (command.uses_gpu_key)
|
||||||
|
glBindTexture(GL_TEXTURE_2D, command.gpu_texture);
|
||||||
|
else if (command.texture_repeat)
|
||||||
|
textures_bind_repeating(&ctx.texture_cache, command.texture_key);
|
||||||
|
else
|
||||||
|
textures_bind(&ctx.texture_cache, command.texture_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.range_start == command.range_end)
|
||||||
|
glDrawElements(GL_TRIANGLES, command.element_count, GL_UNSIGNED_SHORT, NULL);
|
||||||
|
else
|
||||||
|
glDrawRangeElements(GL_TRIANGLES,
|
||||||
|
command.range_start,
|
||||||
|
command.range_end,
|
||||||
|
command.element_count,
|
||||||
|
GL_UNSIGNED_SHORT,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* state clearing */
|
||||||
|
|
||||||
|
if (command.textured)
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
if (command.colors.arity != 0)
|
||||||
|
glDisableClientState(GL_COLOR_ARRAY);
|
||||||
|
|
||||||
|
if (command.texcoords.arity != 0)
|
||||||
|
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||||
|
|
||||||
|
glDisableClientState(GL_VERTEX_ARRAY);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEFERRED_COMMAND_TYPE_USE_PIPIELINE: {
|
||||||
|
switch (deferred_commands[i].use_pipeline.pipeline) {
|
||||||
|
case PIPELINE_2D:
|
||||||
|
finally_use_2d_pipeline();
|
||||||
|
break;
|
||||||
|
case PIPELINE_SPACE:
|
||||||
|
finally_use_space_pipeline();
|
||||||
|
break;
|
||||||
|
case PIPELINE_NO:
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE: {
|
||||||
|
finally_use_texture_mode(deferred_commands[i].use_texture_mode.mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void clear_draw_buffer(void) {
|
||||||
|
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
|
||||||
|
|
||||||
|
DeferredCommand command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_CLEAR,
|
||||||
|
.clear = (DeferredCommandClear) {
|
||||||
|
.clear_color = true,
|
||||||
|
.clear_depth = true,
|
||||||
|
.clear_stencil = true,
|
||||||
|
.color = (Color) { 230, 230, 230, 1 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void start_render_frame(void) {
|
||||||
|
clear_draw_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void end_render_frame(void) {
|
||||||
|
if (!ctx.render_double_buffered || ctx.game.frame_number == 0) {
|
||||||
|
issue_deferred_draw_commands();
|
||||||
|
SDL_GL_SwapWindow(ctx.window);
|
||||||
|
arrsetlen(deferred_commands, 0);
|
||||||
|
restart_scratch_vertex_arrays();
|
||||||
|
} else {
|
||||||
|
/* instead of waiting for frame to finish for the swap, we continue */
|
||||||
|
/* while issuing new state for the next call, but deferring any fragment emitting calls for later */
|
||||||
|
/* actual swap will happen when next frame is fully finished, introducing a delay */
|
||||||
|
SDL_GL_SwapWindow(ctx.window);
|
||||||
|
issue_deferred_draw_commands();
|
||||||
|
restart_scratch_vertex_arrays();
|
||||||
|
glFlush();
|
||||||
|
arrsetlen(deferred_commands, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void use_space_pipeline(void) {
|
void use_space_pipeline(void) {
|
||||||
|
DeferredCommand const command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
|
||||||
|
.use_pipeline = { PIPELINE_SPACE }
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void finally_use_space_pipeline(void) {
|
||||||
if (pipeline_last_used == PIPELINE_SPACE)
|
if (pipeline_last_used == PIPELINE_SPACE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -91,11 +415,22 @@ void use_space_pipeline(void) {
|
|||||||
glMatrixMode(GL_MODELVIEW);
|
glMatrixMode(GL_MODELVIEW);
|
||||||
glLoadMatrixf(&camera_look_at_matrix.row[0].x);
|
glLoadMatrixf(&camera_look_at_matrix.row[0].x);
|
||||||
|
|
||||||
|
texture_mode_last_used = -1;
|
||||||
pipeline_last_used = PIPELINE_SPACE;
|
pipeline_last_used = PIPELINE_SPACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void use_2d_pipeline(void) {
|
void use_2d_pipeline(void) {
|
||||||
|
DeferredCommand const command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
|
||||||
|
.use_pipeline = { PIPELINE_2D }
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void finally_use_2d_pipeline(void) {
|
||||||
if (pipeline_last_used == PIPELINE_SPACE) {
|
if (pipeline_last_used == PIPELINE_SPACE) {
|
||||||
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||||
glFlush();
|
glFlush();
|
||||||
@ -127,80 +462,30 @@ void use_2d_pipeline(void) {
|
|||||||
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
glMatrixMode(GL_PROJECTION);
|
||||||
glLoadIdentity();
|
glLoadIdentity();
|
||||||
glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, -1, 1);
|
glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, 0, 1);
|
||||||
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
glMatrixMode(GL_MODELVIEW);
|
||||||
glLoadIdentity();
|
glLoadIdentity();
|
||||||
|
|
||||||
|
texture_mode_last_used = -1;
|
||||||
pipeline_last_used = PIPELINE_2D;
|
pipeline_last_used = PIPELINE_2D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void upload_quad_vertices(Rect rect) {
|
|
||||||
/* client memory needs to be reachable on glDraw*, so */
|
|
||||||
static float vertices[6 * 2];
|
|
||||||
|
|
||||||
vertices[0] = rect.x; vertices[1] = rect.y;
|
|
||||||
vertices[2] = rect.x; vertices[3] = rect.y + rect.h;
|
|
||||||
vertices[4] = rect.x + rect.w; vertices[5] = rect.y + rect.h;
|
|
||||||
vertices[6] = rect.x + rect.w; vertices[7] = rect.y + rect.h;
|
|
||||||
vertices[8] = rect.x + rect.w; vertices[9] = rect.y;
|
|
||||||
vertices[10] = rect.x; vertices[11] = rect.y;
|
|
||||||
|
|
||||||
glVertexPointer(2, GL_FLOAT, 0, (void *)&vertices);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void render_rectangle(const RectPrimitive *rectangle) {
|
|
||||||
glColor4ub(rectangle->color.r, rectangle->color.g,
|
|
||||||
rectangle->color.b, rectangle->color.a);
|
|
||||||
|
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
|
||||||
upload_quad_vertices(rectangle->rect);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void render_circle(const CirclePrimitive *circle) {
|
|
||||||
SDL_Vertex *vertices = NULL;
|
|
||||||
int *indices = NULL;
|
|
||||||
int num_vertices = (int)circle->radius;
|
|
||||||
|
|
||||||
create_circle_geometry(circle->position,
|
|
||||||
circle->color,
|
|
||||||
circle->radius,
|
|
||||||
num_vertices,
|
|
||||||
&vertices,
|
|
||||||
&indices);
|
|
||||||
|
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
|
||||||
glVertexPointer(2,
|
|
||||||
GL_FLOAT,
|
|
||||||
sizeof (SDL_Vertex),
|
|
||||||
&vertices->position);
|
|
||||||
|
|
||||||
glEnableClientState(GL_COLOR_ARRAY);
|
|
||||||
glColorPointer(4,
|
|
||||||
GL_UNSIGNED_BYTE,
|
|
||||||
sizeof (SDL_Vertex),
|
|
||||||
&vertices->color);
|
|
||||||
|
|
||||||
glDrawElements(GL_TRIANGLES,
|
|
||||||
num_vertices * 3,
|
|
||||||
GL_UNSIGNED_INT,
|
|
||||||
indices);
|
|
||||||
|
|
||||||
glDisableClientState(GL_COLOR_ARRAY);
|
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
|
||||||
|
|
||||||
SDL_free(vertices);
|
|
||||||
SDL_free(indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void use_texture_mode(TextureMode mode) {
|
void use_texture_mode(TextureMode mode) {
|
||||||
|
DeferredCommand const command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
|
||||||
|
.use_texture_mode = { mode }
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void finally_use_texture_mode(TextureMode mode) {
|
||||||
|
if (texture_mode_last_used == mode)
|
||||||
|
return;
|
||||||
|
|
||||||
static GLuint lists = 0;
|
static GLuint lists = 0;
|
||||||
if (!lists) {
|
if (!lists) {
|
||||||
lists = glGenLists(3);
|
lists = glGenLists(3);
|
||||||
@ -209,7 +494,7 @@ void use_texture_mode(TextureMode mode) {
|
|||||||
glNewList(lists + 0, GL_COMPILE); {
|
glNewList(lists + 0, GL_COMPILE); {
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glDepthFunc(GL_ALWAYS);
|
glDepthFunc(GL_LESS);
|
||||||
glDepthMask(GL_FALSE);
|
glDepthMask(GL_FALSE);
|
||||||
glDisable(GL_ALPHA_TEST);
|
glDisable(GL_ALPHA_TEST);
|
||||||
} glEndList();
|
} glEndList();
|
||||||
@ -217,7 +502,7 @@ void use_texture_mode(TextureMode mode) {
|
|||||||
/* seethrough */
|
/* seethrough */
|
||||||
glNewList(lists + 1, GL_COMPILE); {
|
glNewList(lists + 1, GL_COMPILE); {
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
glDepthFunc(GL_LEQUAL);
|
glDepthFunc(GL_LESS);
|
||||||
glDepthMask(GL_TRUE);
|
glDepthMask(GL_TRUE);
|
||||||
glEnable(GL_ALPHA_TEST);
|
glEnable(GL_ALPHA_TEST);
|
||||||
glAlphaFunc(GL_EQUAL, 1.0f);
|
glAlphaFunc(GL_EQUAL, 1.0f);
|
||||||
@ -239,6 +524,8 @@ void use_texture_mode(TextureMode mode) {
|
|||||||
} else {
|
} else {
|
||||||
glCallList(lists + 2);
|
glCallList(lists + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
texture_mode_last_used = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -258,11 +545,11 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
|
|||||||
|
|
||||||
|
|
||||||
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
||||||
void *bytes, size_t size) {
|
void const *bytes, size_t size) {
|
||||||
if (builder->bytes_left == 0)
|
if (builder->bytes_left == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
SDL_memcpy(builder->mapping, bytes, size);
|
memcpy(builder->mapping, bytes, size);
|
||||||
builder->bytes_left -= size;
|
builder->bytes_left -= size;
|
||||||
|
|
||||||
/* trigger data send */
|
/* trigger data send */
|
||||||
@ -277,92 +564,107 @@ bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void finally_render_sprites(const Primitive2D primitives[],
|
void finally_render_quads(const Primitive2D primitives[],
|
||||||
const struct SpriteBatch batch,
|
const struct QuadBatch batch,
|
||||||
const VertexBuffer buffer)
|
const VertexBuffer buffer)
|
||||||
{
|
{
|
||||||
(void)buffer;
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
GLsizei off;
|
GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
|
||||||
GLsizei voff;
|
|
||||||
GLsizei uvoff;
|
|
||||||
|
|
||||||
if (!batch.constant_colored) {
|
if (!batch.constant_colored && batch.textured) {
|
||||||
off = offsetof(ElementIndexedQuad, v1);
|
off = offsetof(ElementIndexedQuad, v1);
|
||||||
voff = offsetof(ElementIndexedQuad, v0);
|
voff = offsetof(ElementIndexedQuad, v0);
|
||||||
uvoff = offsetof(ElementIndexedQuad, uv0);
|
uvoff = offsetof(ElementIndexedQuad, uv0);
|
||||||
} else {
|
coff = offsetof(ElementIndexedQuad, c0);
|
||||||
|
} else if (batch.constant_colored && batch.textured) {
|
||||||
off = offsetof(ElementIndexedQuadWithoutColor, v1);
|
off = offsetof(ElementIndexedQuadWithoutColor, v1);
|
||||||
voff = offsetof(ElementIndexedQuadWithoutColor, v0);
|
voff = offsetof(ElementIndexedQuadWithoutColor, v0);
|
||||||
uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
|
uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
|
||||||
|
} else if (!batch.constant_colored && !batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutTexture, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutTexture, v0);
|
||||||
|
coff = offsetof(ElementIndexedQuad, c0);
|
||||||
|
} else if (batch.constant_colored && !batch.textured) {
|
||||||
|
off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1);
|
||||||
|
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vertex specification */
|
command.vertices = (AttributeArrayPointer) {
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
.arity = 2,
|
||||||
glVertexPointer(2,
|
.type = GL_FLOAT,
|
||||||
GL_FLOAT,
|
.stride = off,
|
||||||
off,
|
.offset = voff,
|
||||||
(void *)(size_t)voff);
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
if (batch.textured)
|
||||||
glClientActiveTexture(GL_TEXTURE0);
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
glTexCoordPointer(2,
|
.arity = 2,
|
||||||
GL_FLOAT,
|
.type = GL_FLOAT,
|
||||||
off,
|
.stride = off,
|
||||||
(void *)(size_t)uvoff);
|
.offset = uvoff,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
if (!batch.constant_colored) {
|
if (!batch.constant_colored) {
|
||||||
glEnableClientState(GL_COLOR_ARRAY);
|
command.colors = (AttributeArrayPointer) {
|
||||||
glColorPointer(4,
|
.arity = 4,
|
||||||
GL_UNSIGNED_BYTE,
|
.type = GL_UNSIGNED_BYTE,
|
||||||
off,
|
.stride = off,
|
||||||
(void *)offsetof(ElementIndexedQuad, c0));
|
.offset = coff,
|
||||||
} else
|
.buffer = buffer
|
||||||
glColor4ub(primitives[0].sprite.color.r,
|
};
|
||||||
primitives[0].sprite.color.g,
|
} else {
|
||||||
primitives[0].sprite.color.b,
|
command.constant_colored = true;
|
||||||
primitives[0].sprite.color.a);
|
command.color = primitives[0].sprite.color;
|
||||||
|
}
|
||||||
|
|
||||||
if (!batch.repeat)
|
if (batch.textured) {
|
||||||
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key);
|
/* TODO: bad, don't */
|
||||||
else
|
command.textured = true;
|
||||||
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key);
|
command.texture_key = primitives->sprite.texture_key;
|
||||||
|
command.texture_repeat = batch.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
bind_quad_element_buffer();
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (GLsizei)batch.size;
|
||||||
|
command.range_end = 6 * (GLsizei)batch.size;
|
||||||
|
|
||||||
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, GL_UNSIGNED_SHORT, NULL);
|
use_texture_mode(batch.mode);
|
||||||
|
|
||||||
/* clear the state */
|
DeferredCommand final_command = {
|
||||||
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
arrpush(deferred_commands, final_command);
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
|
||||||
|
|
||||||
if (!batch.constant_colored)
|
|
||||||
glDisableClientState(GL_COLOR_ARRAY);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
size_t get_sprite_payload_size(struct SpriteBatch batch) {
|
size_t get_quad_payload_size(struct QuadBatch batch) {
|
||||||
if (batch.constant_colored)
|
if (batch.constant_colored && batch.textured)
|
||||||
return sizeof (ElementIndexedQuadWithoutColor);
|
return sizeof (ElementIndexedQuadWithoutColor);
|
||||||
else
|
else if (!batch.constant_colored && batch.textured)
|
||||||
return sizeof (ElementIndexedQuad);
|
return sizeof (ElementIndexedQuad);
|
||||||
|
else if (batch.constant_colored && !batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutTexture);
|
||||||
|
else if (!batch.constant_colored && !batch.textured)
|
||||||
|
return sizeof (ElementIndexedQuadWithoutColorWithoutTexture);
|
||||||
|
|
||||||
|
SDL_assert(false);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
|
bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
|
||||||
VertexBufferBuilder *builder,
|
VertexBufferBuilder *builder,
|
||||||
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
||||||
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||||
Color color)
|
Color color)
|
||||||
{
|
{
|
||||||
if (!batch.constant_colored) {
|
if (!batch.constant_colored && batch.textured) {
|
||||||
ElementIndexedQuad buffer_element = {
|
ElementIndexedQuad const buffer_element = {
|
||||||
.v0 = v0,
|
.v0 = v0,
|
||||||
.v1 = v1,
|
.v1 = v1,
|
||||||
.v2 = v2,
|
.v2 = v2,
|
||||||
@ -375,15 +677,15 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
|
|||||||
|
|
||||||
/* equal for all (flat shaded) */
|
/* equal for all (flat shaded) */
|
||||||
.c0 = color,
|
.c0 = color,
|
||||||
.c1 = color,
|
// .c1 = color,
|
||||||
.c2 = color,
|
.c2 = color,
|
||||||
.c3 = color,
|
// .c3 = color,
|
||||||
};
|
};
|
||||||
|
|
||||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
|
||||||
} else {
|
} else if (batch.constant_colored && batch.textured) {
|
||||||
ElementIndexedQuadWithoutColor buffer_element = {
|
ElementIndexedQuadWithoutColor const buffer_element = {
|
||||||
.v0 = v0,
|
.v0 = v0,
|
||||||
.v1 = v1,
|
.v1 = v1,
|
||||||
.v2 = v2,
|
.v2 = v2,
|
||||||
@ -395,8 +697,37 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
|
|||||||
.uv3 = uv3,
|
.uv3 = uv3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
|
||||||
|
} else if (!batch.constant_colored && !batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutTexture const buffer_element = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
|
||||||
|
/* equal for all (flat shaded) */
|
||||||
|
.c0 = color,
|
||||||
|
// .c1 = color,
|
||||||
|
.c2 = color,
|
||||||
|
// .c3 = color,
|
||||||
|
};
|
||||||
|
|
||||||
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
|
|
||||||
|
} else if (batch.constant_colored && !batch.textured) {
|
||||||
|
ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = {
|
||||||
|
.v0 = v0,
|
||||||
|
.v1 = v1,
|
||||||
|
.v2 = v2,
|
||||||
|
.v3 = v3,
|
||||||
|
};
|
||||||
|
|
||||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_assert(false);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -465,44 +796,46 @@ void finally_draw_text(FontData const *font_data,
|
|||||||
Color color,
|
Color color,
|
||||||
VertexBuffer buffer)
|
VertexBuffer buffer)
|
||||||
{
|
{
|
||||||
(void)buffer;
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
/* vertex specification */
|
command.vertices = (AttributeArrayPointer) {
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
.arity = 2,
|
||||||
glVertexPointer(2,
|
.type = GL_FLOAT,
|
||||||
GL_FLOAT,
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||||
offsetof(ElementIndexedQuadWithoutColor, v1),
|
.offset = offsetof(ElementIndexedQuadWithoutColor, v0),
|
||||||
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, v0));
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
command.texcoords = (AttributeArrayPointer) {
|
||||||
glClientActiveTexture(GL_TEXTURE0);
|
.arity = 2,
|
||||||
glTexCoordPointer(2,
|
.type = GL_FLOAT,
|
||||||
GL_FLOAT,
|
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||||
offsetof(ElementIndexedQuadWithoutColor, v1),
|
.offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
|
||||||
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0));
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
bind_quad_element_buffer();
|
command.constant_colored = true;
|
||||||
|
command.color = color;
|
||||||
|
|
||||||
|
command.gpu_texture = font_data->texture;
|
||||||
|
command.uses_gpu_key = true;
|
||||||
|
command.textured = true;
|
||||||
|
|
||||||
|
command.element_buffer = get_quad_element_buffer();
|
||||||
|
command.element_count = 6 * (GLsizei)len;
|
||||||
|
command.range_end = 6 * (GLsizei)len;
|
||||||
|
|
||||||
use_texture_mode(TEXTURE_MODE_GHOSTLY);
|
use_texture_mode(TEXTURE_MODE_GHOSTLY);
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, font_data->texture);
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
glColor4ub(color.r, color.g, color.b, color.a);
|
arrpush(deferred_commands, final_command);
|
||||||
|
|
||||||
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)len, GL_UNSIGNED_SHORT, NULL);
|
|
||||||
|
|
||||||
/* clear the state */
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
||||||
|
|
||||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
||||||
glDisableClientState(GL_VERTEX_ARRAY);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
||||||
|
|
||||||
/* TODO: why doesn't it get restored if not placed here? */
|
/* TODO: why doesn't it get restored if not placed here? */
|
||||||
glDepthMask(GL_TRUE);
|
// glDepthMask(GL_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -531,6 +864,47 @@ static void load_cubemap_side(const char *path, GLenum target) {
|
|||||||
SDL_FreeSurface(surface);
|
SDL_FreeSurface(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void render_circle(const CirclePrimitive *circle) {
|
||||||
|
static Vec2 vertices[CIRCLE_VERTICES_MAX];
|
||||||
|
int num_vertices = CIRCLE_VERTICES_MAX - 1;
|
||||||
|
|
||||||
|
create_circle_geometry(circle->position,
|
||||||
|
circle->radius,
|
||||||
|
num_vertices,
|
||||||
|
vertices);
|
||||||
|
|
||||||
|
VertexBuffer buffer = get_scratch_vertex_array();
|
||||||
|
specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);
|
||||||
|
|
||||||
|
DeferredCommandDraw command = {0};
|
||||||
|
|
||||||
|
command.vertices = (AttributeArrayPointer) {
|
||||||
|
.arity = 2,
|
||||||
|
.type = GL_FLOAT,
|
||||||
|
.stride = sizeof (Vec2),
|
||||||
|
.offset = 0,
|
||||||
|
.buffer = buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
command.constant_colored = true;
|
||||||
|
command.color = circle->color;
|
||||||
|
|
||||||
|
command.element_buffer = get_circle_element_buffer();
|
||||||
|
command.element_count = num_vertices * 3;
|
||||||
|
command.range_end = num_vertices * 3;
|
||||||
|
|
||||||
|
use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY);
|
||||||
|
|
||||||
|
DeferredCommand final_command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||||
|
.draw = command
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, final_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void finally_render_skybox(char *paths) {
|
void finally_render_skybox(char *paths) {
|
||||||
static GLuint cubemap = 0;
|
static GLuint cubemap = 0;
|
||||||
static char *paths_cache = NULL;
|
static char *paths_cache = NULL;
|
||||||
@ -697,3 +1071,16 @@ void finally_apply_fog(float start, float end, float density, Color color) {
|
|||||||
void finally_pop_fog(void) {
|
void finally_pop_fog(void) {
|
||||||
glDisable(GL_FOG);
|
glDisable(GL_FOG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void set_depth_range(double low, double high) {
|
||||||
|
DeferredCommand const command = {
|
||||||
|
.type = DEFERRED_COMMAND_TYPE_DEPTH_RANGE,
|
||||||
|
.depth_range = {
|
||||||
|
.low = low,
|
||||||
|
.high = high
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
arrpush(deferred_commands, command);
|
||||||
|
}
|
||||||
|
@ -26,65 +26,66 @@ void delete_vertex_buffer(VertexBuffer buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void specify_vertex_buffer(VertexBuffer buffer, void *data, size_t bytes) {
|
void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes) {
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||||
glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void bind_quad_element_buffer(void) {
|
VertexBuffer get_quad_element_buffer(void) {
|
||||||
static GLuint buffer = 0;
|
static VertexBuffer buffer = 0;
|
||||||
|
|
||||||
/* it's only generated once at runtime */
|
/* it's only generated once at runtime */
|
||||||
|
/* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */
|
||||||
if (buffer == 0) {
|
if (buffer == 0) {
|
||||||
glGenBuffers(1, &buffer);
|
buffer = create_vertex_buffer();
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
|
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
||||||
QUAD_ELEMENT_BUFFER_LENGTH * 6 * sizeof(uint16_t),
|
|
||||||
NULL,
|
|
||||||
GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
|
||||||
GL_WRITE_ONLY);
|
GLshort indices[6];
|
||||||
if (!indices)
|
indices[0] = (GLshort)(i * 4 + 0);
|
||||||
CRY("Quad indices generation", "glMapBuffer() failed");
|
indices[1] = (GLshort)(i * 4 + 1);
|
||||||
|
indices[2] = (GLshort)(i * 4 + 2);
|
||||||
|
indices[3] = (GLshort)(i * 4 + 2);
|
||||||
|
indices[4] = (GLshort)(i * 4 + 3);
|
||||||
|
indices[5] = (GLshort)(i * 4 + 0);
|
||||||
|
|
||||||
for (uint16_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
|
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
|
||||||
indices[i * 6 + 0] = (uint16_t)(i * 4 + 0);
|
|
||||||
indices[i * 6 + 1] = (uint16_t)(i * 4 + 1);
|
|
||||||
indices[i * 6 + 2] = (uint16_t)(i * 4 + 2);
|
|
||||||
indices[i * 6 + 3] = (uint16_t)(i * 4 + 2);
|
|
||||||
indices[i * 6 + 4] = (uint16_t)(i * 4 + 3);
|
|
||||||
indices[i * 6 + 5] = (uint16_t)(i * 4 + 0);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
|
SDL_assert_always(buffer);
|
||||||
|
|
||||||
} else
|
return buffer;
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void clear_draw_buffer(void) {
|
VertexBuffer get_circle_element_buffer(void) {
|
||||||
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
|
static VertexBuffer buffer = 0;
|
||||||
glClearColor((1.0f / 255) * 230,
|
|
||||||
(1.0f / 255) * 230,
|
|
||||||
(1.0f / 255) * 230, 1);
|
|
||||||
|
|
||||||
/* TODO: don't clear color when skybox is applied? */
|
if (buffer == 0) {
|
||||||
/* for that window should match framebuffer */
|
buffer = create_vertex_buffer();
|
||||||
/* also it is driver dependent, from what i can gather */
|
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * CIRCLE_ELEMENT_BUFFER_LENGTH);
|
||||||
glClear(GL_COLOR_BUFFER_BIT |
|
|
||||||
GL_DEPTH_BUFFER_BIT |
|
for (size_t i = 1; i < CIRCLE_VERTICES_MAX; ++i) {
|
||||||
GL_STENCIL_BUFFER_BIT);
|
/* first one is center point index, always zero */
|
||||||
}
|
GLshort indices[3];
|
||||||
|
|
||||||
|
indices[0] = 0;
|
||||||
void swap_buffers(void) {
|
|
||||||
SDL_GL_SwapWindow(ctx.window);
|
/* generated point index */
|
||||||
}
|
indices[1] = (GLshort)i;
|
||||||
|
|
||||||
|
size_t index = (i + 1) % (CIRCLE_VERTICES_MAX - 1);
|
||||||
void set_depth_range(double low, double high) {
|
if (index == 0) /* don't use center for outer ring */
|
||||||
glDepthRange(low, high);
|
index = (CIRCLE_VERTICES_MAX - 1);
|
||||||
|
indices[2] = (GLshort)index;
|
||||||
|
|
||||||
|
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_assert_always(buffer);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
30
src/rendering/twn_quads.c
Normal file
30
src/rendering/twn_quads.c
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include "twn_draw_c.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) {
|
||||||
|
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
|
||||||
|
return collect_sprite_batch(primitives, len);
|
||||||
|
else if (primitives[0].type == PRIMITIVE_2D_RECT)
|
||||||
|
return collect_rect_batch(primitives, len);
|
||||||
|
else
|
||||||
|
SDL_assert(false);
|
||||||
|
|
||||||
|
return (struct QuadBatch){0};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* assumes that orthogonal matrix setup is done already */
|
||||||
|
void render_quad_batch(const Primitive2D primitives[],
|
||||||
|
const struct QuadBatch batch)
|
||||||
|
{
|
||||||
|
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
|
||||||
|
render_sprite_batch(primitives, batch);
|
||||||
|
else if (primitives[0].type == PRIMITIVE_2D_RECT)
|
||||||
|
render_rect_batch(primitives, batch);
|
||||||
|
else
|
||||||
|
SDL_assert(false);
|
||||||
|
|
||||||
|
return (struct QuadBatch){0};
|
||||||
|
}
|
102
src/rendering/twn_rects.c
Normal file
102
src/rendering/twn_rects.c
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#include "twn_draw.h"
|
||||||
|
#include "twn_draw_c.h"
|
||||||
|
#include "twn_engine_context_c.h"
|
||||||
|
#include "twn_util.h"
|
||||||
|
#include "twn_util_c.h"
|
||||||
|
#include "twn_textures_c.h"
|
||||||
|
#include "twn_option.h"
|
||||||
|
|
||||||
|
#include <stb_ds.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
|
||||||
|
void draw_rectangle(Rect rect, Color color) {
|
||||||
|
RectPrimitive rectangle = {
|
||||||
|
.rect = rect,
|
||||||
|
.color = color,
|
||||||
|
};
|
||||||
|
|
||||||
|
Primitive2D primitive = {
|
||||||
|
.type = PRIMITIVE_2D_RECT,
|
||||||
|
.rect = rectangle,
|
||||||
|
};
|
||||||
|
|
||||||
|
arrput(ctx.render_queue_2d, primitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len) {
|
||||||
|
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
|
||||||
|
SDL_assert(primitives && len != 0);
|
||||||
|
|
||||||
|
struct QuadBatch batch = {
|
||||||
|
.mode = primitives[0].rect.color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY,
|
||||||
|
.constant_colored = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].rect.color;
|
||||||
|
|
||||||
|
/* batch size is clamped so that reallocated short indices could be used */
|
||||||
|
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
|
||||||
|
len = QUAD_ELEMENT_BUFFER_LENGTH;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
const Primitive2D *const current = &primitives[i];
|
||||||
|
|
||||||
|
/* don't touch things other than rectangles */
|
||||||
|
if (current->type != PRIMITIVE_2D_RECT)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* only collect the same blend modes */
|
||||||
|
if ((current->rect.color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY) != batch.mode)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* if all are modulated the same we can skip sending the color data */
|
||||||
|
if (*(const uint32_t *)¤t->rect.color != uniform_color)
|
||||||
|
batch.constant_colored = false;
|
||||||
|
|
||||||
|
++batch.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* assumes that orthogonal matrix setup is done already */
|
||||||
|
void render_rect_batch(const Primitive2D primitives[],
|
||||||
|
const struct QuadBatch batch)
|
||||||
|
{
|
||||||
|
SDL_assert(primitives && batch.size != 0);
|
||||||
|
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
|
||||||
|
|
||||||
|
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
||||||
|
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||||
|
|
||||||
|
use_texture_mode(batch.mode);
|
||||||
|
|
||||||
|
/* vertex population over a vertex buffer builder interface */
|
||||||
|
{
|
||||||
|
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < batch.size; ++i) {
|
||||||
|
/* render opaques front to back, to gain benefit of an early z rejection */
|
||||||
|
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
|
||||||
|
const RectPrimitive rect = primitives[cur].rect;
|
||||||
|
|
||||||
|
Vec2 v0 = { rect.rect.x, rect.rect.y };
|
||||||
|
Vec2 v1 = { rect.rect.x, rect.rect.y + rect.rect.h };
|
||||||
|
Vec2 v2 = { rect.rect.x + rect.rect.w, rect.rect.y + rect.rect.h };
|
||||||
|
Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
|
||||||
|
|
||||||
|
push_quad_payload_to_vertex_buffer_builder(
|
||||||
|
batch, &payload,
|
||||||
|
v0, v1, v2, v3,
|
||||||
|
(Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
|
||||||
|
rect.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finally_render_quads(primitives, batch, vertex_array);
|
||||||
|
}
|
@ -60,15 +60,18 @@ void draw_sprite_args(const DrawSpriteArgs args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct SpriteBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) {
|
struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) {
|
||||||
/* assumes that first primitive is already a sprite */
|
SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
|
||||||
|
SDL_assert(primitives && len != 0);
|
||||||
|
|
||||||
const uint16_t texture_key_id = primitives[0].sprite.texture_key.id;
|
const uint16_t texture_key_id = primitives[0].sprite.texture_key.id;
|
||||||
const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key);
|
const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key);
|
||||||
|
|
||||||
struct SpriteBatch batch = {
|
struct QuadBatch batch = {
|
||||||
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
|
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
|
||||||
.constant_colored = true,
|
.constant_colored = true,
|
||||||
.repeat = primitives[0].sprite.repeat,
|
.repeat = primitives[0].sprite.repeat,
|
||||||
|
.textured = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
|
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
|
||||||
@ -116,35 +119,40 @@ struct SpriteBatch collect_sprite_batch(const Primitive2D primitives[], size_t l
|
|||||||
|
|
||||||
|
|
||||||
/* assumes that orthogonal matrix setup is done already */
|
/* assumes that orthogonal matrix setup is done already */
|
||||||
void render_sprites(const Primitive2D primitives[],
|
void render_sprite_batch(const Primitive2D primitives[],
|
||||||
const struct SpriteBatch batch)
|
const struct QuadBatch batch)
|
||||||
{
|
{
|
||||||
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
SDL_assert(primitives && batch.size != 0);
|
||||||
static VertexBuffer vertex_array = 0;
|
SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
|
||||||
if (vertex_array == 0)
|
|
||||||
vertex_array = create_vertex_buffer();
|
|
||||||
|
|
||||||
use_texture_mode(batch.mode);
|
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||||
|
|
||||||
const Rect dims =
|
const Rect dims =
|
||||||
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
|
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||||
|
|
||||||
|
/* cached srcrect */
|
||||||
|
Rect cached_srcrect;
|
||||||
|
TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
|
||||||
|
|
||||||
/* vertex population over a vertex buffer builder interface */
|
/* vertex population over a vertex buffer builder interface */
|
||||||
{
|
{
|
||||||
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_sprite_payload_size(batch) * batch.size);
|
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
|
||||||
|
|
||||||
for (size_t i = 0; i < batch.size; ++i) {
|
for (size_t i = 0; i < batch.size; ++i) {
|
||||||
/* render opaques front to back */
|
/* render opaques front to back, to gain benefit of an early z rejection */
|
||||||
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
|
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
|
||||||
const SpritePrimitive sprite = primitives[cur].sprite;
|
const SpritePrimitive sprite = primitives[cur].sprite;
|
||||||
|
|
||||||
/* TODO: try caching it */
|
if (primitives[cur].sprite.texture_key.id != cached_srcrect_key.id) {
|
||||||
const Rect srcrect =
|
cached_srcrect = textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
|
||||||
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
|
cached_srcrect_key = primitives[cur].sprite.texture_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect const srcrect = cached_srcrect;
|
||||||
|
|
||||||
Vec2 uv0, uv1, uv2, uv3;
|
Vec2 uv0, uv1, uv2, uv3;
|
||||||
|
|
||||||
if (!sprite.repeat) {
|
if (!batch.repeat) {
|
||||||
const float wr = srcrect.w / dims.w;
|
const float wr = srcrect.w / dims.w;
|
||||||
const float hr = srcrect.h / dims.h;
|
const float hr = srcrect.h / dims.h;
|
||||||
const float xr = srcrect.x / dims.w;
|
const float xr = srcrect.x / dims.w;
|
||||||
@ -233,9 +241,9 @@ void render_sprites(const Primitive2D primitives[],
|
|||||||
v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
|
v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
push_sprite_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
|
push_quad_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finally_render_sprites(primitives, batch, vertex_array);
|
finally_render_quads(primitives, batch, vertex_array);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,19 @@ typedef struct StringArena {
|
|||||||
static StringArena string_arena;
|
static StringArena string_arena;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct FontFileBuffer {
|
||||||
|
size_t len;
|
||||||
|
unsigned char *buffer;
|
||||||
|
} FontFileBuffer;
|
||||||
|
|
||||||
|
typedef struct FontFileCacheItem {
|
||||||
|
char *key;
|
||||||
|
FontFileBuffer value;
|
||||||
|
} FontFileCacheItem;
|
||||||
|
|
||||||
|
static FontFileCacheItem *font_file_cache_hash;
|
||||||
|
|
||||||
|
|
||||||
static void string_arena_init(StringArena *arena) {
|
static void string_arena_init(StringArena *arena) {
|
||||||
arena->head = cmalloc(sizeof *arena->head);
|
arena->head = cmalloc(sizeof *arena->head);
|
||||||
arena->current_block = arena->head;
|
arena->current_block = arena->head;
|
||||||
@ -102,13 +115,23 @@ static FontData *text_load_font_data(const char *path, int height_px) {
|
|||||||
unsigned char* bitmap = ccalloc(ctx.font_texture_size * ctx.font_texture_size, 1);
|
unsigned char* bitmap = ccalloc(ctx.font_texture_size * ctx.font_texture_size, 1);
|
||||||
|
|
||||||
{
|
{
|
||||||
unsigned char *buf = NULL;
|
unsigned char *buf = NULL;
|
||||||
int64_t buf_len = file_to_bytes(path, &buf);
|
int64_t buf_len = 0;
|
||||||
|
|
||||||
|
/* if the file was already loaded just get it */
|
||||||
|
FontFileCacheItem *font_file_ptr = shgetp_null(font_file_cache_hash, path);
|
||||||
|
if (font_file_ptr != NULL) {
|
||||||
|
buf = font_file_ptr->value.buffer;
|
||||||
|
buf_len = font_file_ptr->value.len;
|
||||||
|
} else {
|
||||||
|
buf_len = file_to_bytes(path, &buf);
|
||||||
|
FontFileBuffer buffer = { buf_len, buf };
|
||||||
|
shput(font_file_cache_hash, path, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0));
|
stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0));
|
||||||
|
|
||||||
/* might as well get these now, for later */
|
/* might as well get these now, for later */
|
||||||
font_data->file_bytes = buf;
|
|
||||||
font_data->file_bytes_len = buf_len;
|
|
||||||
font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px);
|
font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px);
|
||||||
stbtt_GetFontVMetrics(
|
stbtt_GetFontVMetrics(
|
||||||
&font_data->info,
|
&font_data->info,
|
||||||
@ -142,16 +165,13 @@ static FontData *text_load_font_data(const char *path, int height_px) {
|
|||||||
|
|
||||||
|
|
||||||
static void text_destroy_font_data(FontData *font_data) {
|
static void text_destroy_font_data(FontData *font_data) {
|
||||||
SDL_free(font_data->file_bytes);
|
|
||||||
delete_gpu_texture(font_data->texture);
|
delete_gpu_texture(font_data->texture);
|
||||||
SDL_free(font_data);
|
SDL_free(font_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) {
|
static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) {
|
||||||
VertexBuffer vertex_array = 0;
|
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||||
if (vertex_array == 0)
|
|
||||||
vertex_array = create_vertex_buffer();
|
|
||||||
|
|
||||||
const size_t len = SDL_strlen(text);
|
const size_t len = SDL_strlen(text);
|
||||||
|
|
||||||
@ -192,7 +212,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
|
|||||||
|
|
||||||
|
|
||||||
static void ensure_font_cache(const char *font_path, int height_px) {
|
static void ensure_font_cache(const char *font_path, int height_px) {
|
||||||
/* HACK: stupid, bad, don't do this */
|
/* HACK: don't */
|
||||||
bool is_cached = false;
|
bool is_cached = false;
|
||||||
for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
|
for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
|
||||||
FontData *font_data = ctx.text_cache.data[i];
|
FontData *font_data = ctx.text_cache.data[i];
|
||||||
@ -229,6 +249,8 @@ void render_text(const TextPrimitive *text) {
|
|||||||
void text_cache_init(TextCache *cache) {
|
void text_cache_init(TextCache *cache) {
|
||||||
arrsetlen(cache->data, 0);
|
arrsetlen(cache->data, 0);
|
||||||
string_arena_init(&string_arena);
|
string_arena_init(&string_arena);
|
||||||
|
|
||||||
|
sh_new_arena(font_file_cache_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -237,8 +259,13 @@ void text_cache_deinit(TextCache *cache) {
|
|||||||
text_destroy_font_data(ctx.text_cache.data[i]);
|
text_destroy_font_data(ctx.text_cache.data[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfree(cache->data);
|
for (size_t i = 0; i < shlenu(font_file_cache_hash); ++i) {
|
||||||
|
SDL_free(font_file_cache_hash[i].value.buffer);
|
||||||
|
}
|
||||||
|
shfree(font_file_cache_hash);
|
||||||
|
|
||||||
string_arena_deinit(&string_arena);
|
string_arena_deinit(&string_arena);
|
||||||
|
arrfree(cache->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,8 +22,6 @@ typedef struct FontData {
|
|||||||
stbtt_fontinfo info;
|
stbtt_fontinfo info;
|
||||||
|
|
||||||
const char *file_path;
|
const char *file_path;
|
||||||
unsigned char *file_bytes;
|
|
||||||
size_t file_bytes_len;
|
|
||||||
|
|
||||||
GPUTexture texture;
|
GPUTexture texture;
|
||||||
|
|
||||||
|
@ -45,9 +45,7 @@ void draw_triangle(const char *path,
|
|||||||
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
|
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
|
||||||
TextureKey texture_key)
|
TextureKey texture_key)
|
||||||
{
|
{
|
||||||
static VertexBuffer vertex_array = 0;
|
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||||
if (vertex_array == 0)
|
|
||||||
vertex_array = create_vertex_buffer();
|
|
||||||
|
|
||||||
const size_t primitives_len = arrlenu(batch->primitives);
|
const size_t primitives_len = arrlenu(batch->primitives);
|
||||||
|
|
||||||
|
@ -15,5 +15,6 @@
|
|||||||
#include "rendering/twn_fog.c"
|
#include "rendering/twn_fog.c"
|
||||||
#include "rendering/twn_skybox.c"
|
#include "rendering/twn_skybox.c"
|
||||||
#include "rendering/twn_sprites.c"
|
#include "rendering/twn_sprites.c"
|
||||||
|
#include "rendering/twn_rects.c"
|
||||||
#include "rendering/twn_text.c"
|
#include "rendering/twn_text.c"
|
||||||
#include "rendering/twn_triangles.c"
|
#include "rendering/twn_triangles.c"
|
||||||
|
@ -77,6 +77,7 @@ typedef struct EngineContext {
|
|||||||
bool window_size_has_changed;
|
bool window_size_has_changed;
|
||||||
bool resync_flag;
|
bool resync_flag;
|
||||||
bool was_successful;
|
bool was_successful;
|
||||||
|
bool render_double_buffered;
|
||||||
} EngineContext;
|
} EngineContext;
|
||||||
|
|
||||||
/* TODO: does it need to be marked with TWN_API? */
|
/* TODO: does it need to be marked with TWN_API? */
|
||||||
|
@ -658,6 +658,8 @@ static bool initialize(void) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
ctx.render_double_buffered = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
@ -50,6 +50,7 @@ typedef struct TextureCache {
|
|||||||
typedef struct TextureKey { uint16_t id; } TextureKey;
|
typedef struct TextureKey { uint16_t id; } TextureKey;
|
||||||
|
|
||||||
/* tests whether given key structure corresponds to any texture */
|
/* tests whether given key structure corresponds to any texture */
|
||||||
|
#define TEXTURE_KEY_INVALID (TextureKey) { (uint16_t)-1 }
|
||||||
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1)
|
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1)
|
||||||
|
|
||||||
void textures_cache_init(struct TextureCache *cache, SDL_Window *window);
|
void textures_cache_init(struct TextureCache *cache, SDL_Window *window);
|
||||||
|
@ -39,7 +39,7 @@ static inline float fast_sqrt(float x)
|
|||||||
|
|
||||||
|
|
||||||
static inline Vec2 fast_cossine(float a) {
|
static inline Vec2 fast_cossine(float a) {
|
||||||
const float s = SDL_sinf(a);
|
const float s = sinf(a);
|
||||||
return (Vec2){
|
return (Vec2){
|
||||||
.x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1),
|
.x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1),
|
||||||
.y = s
|
.y = s
|
||||||
|
Reference in New Issue
Block a user