Compare commits

..

No commits in common. "7e409fc14a38e108e2ec1df53a0983f204246af8" and "446402c2e00ef9f80136b75a3af9c170d278b29a" have entirely different histories.

14 changed files with 327 additions and 610 deletions

View File

@ -8,6 +8,7 @@
/* 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;
@ -15,6 +16,7 @@ typedef struct Vec2i {
/* 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;
@ -23,6 +25,7 @@ typedef struct Vec2 {
/* 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;
@ -32,6 +35,7 @@ typedef struct Vec3 {
/* 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;
@ -41,6 +45,7 @@ typedef struct Vec4 {
/* 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;
@ -50,6 +55,7 @@ typedef struct Color {
/* 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;
@ -59,6 +65,7 @@ typedef struct Recti {
/* 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;

View File

@ -1,3 +1,4 @@
#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"
@ -23,15 +24,22 @@ void draw_circle(Vec2 position, float radius, Color color) {
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,
Vec2 vertices[]) SDL_Vertex vertices[],
int indices[])
{ {
/* 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].x = (float)position.x; vertices[0].position.x = (float)position.x;
vertices[0].y = (float)position.y; vertices[0].position.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;
@ -40,13 +48,34 @@ 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;
float c, s; vertices[i].position.x =
sincosf(final_seg_rotation_angle, &s, &c); cosf(final_seg_rotation_angle) * start_x -
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].x = c * start_x - s * start_y; vertices[i].position.x += position.x;
vertices[i].y = c * start_y + s * start_x; vertices[i].position.y += position.y;
vertices[i].x += position.x; vertices[i].color.r = color.r;
vertices[i].y += position.y; vertices[i].color.g = color.g;
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;
} }
} }

View File

@ -374,11 +374,11 @@ void render(void) {
} }
} }
start_render_frame(); {
render_space(); render_space();
render_skybox(); /* after space, as to use depth buffer for early z rejection */ render_skybox(); /* after space, as to use depth buffer for early rejection */
render_2d(); render_2d();
} end_render_frame(); swap_buffers();
clear_draw_buffer();
} }

View File

@ -20,10 +20,7 @@
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;
@ -132,9 +129,11 @@ void render_queue_clear(void);
/* fills two existing arrays with the geometry data of a circle */ /* 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 */ /* 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,
Vec2 vertices[]); SDL_Vertex vertices[],
int indices[]);
struct QuadBatch { struct QuadBatch {
size_t size; /* how many primitives are in current batch */ size_t size; /* how many primitives are in current batch */
@ -166,8 +165,6 @@ 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 const *data, size_t bytes); void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
@ -190,9 +187,7 @@ void swap_buffers(void);
void set_depth_range(double low, double high); void set_depth_range(double low, double high);
VertexBuffer get_quad_element_buffer(void); void bind_quad_element_buffer(void);
VertexBuffer get_circle_element_buffer(void);
void render_circle(const CirclePrimitive *circle); void render_circle(const CirclePrimitive *circle);
@ -243,8 +238,4 @@ 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

View File

@ -9,113 +9,92 @@
#include <stb_ds.h> #include <stb_ds.h>
typedef struct {
float x, y;
} Vec2Packed;
typedef struct {
int16_t x, y;
} Vec2ShortPacked;
typedef struct {
uint8_t r, g, b, a;
} ColorPacked;
#define m_vec2_packed_from(p_vec2) ((Vec2Packed){ (p_vec2).x, (p_vec2).y })
#define m_vec2_short_packed_from(p_vec2) ((Vec2ShortPacked){ (uint16_t)((p_vec2).x * 32768 * 2), (uint16_t)((p_vec2).y * 32768 * 2) })
#define m_color_packed_from(p_color) ((ColorPacked){ (p_color).r, (p_color).g, (p_color).b, (p_color).a })
/* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */ /* 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 */ /* 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 */
typedef struct ElementIndexedQuad { typedef struct ElementIndexedQuad {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2Packed v0;
Vec2 uv0; Vec2Packed uv0;
Color c0; ColorPacked c0;
/* bottom-left */ /* bottom-left */
Vec2 v1; Vec2Packed v1;
Vec2 uv1; Vec2Packed uv1;
Color c1; ColorPacked c1;
/* bottom-right */ /* bottom-right */
Vec2 v2; Vec2Packed v2;
Vec2 uv2; Vec2Packed uv2;
Color c2; ColorPacked c2;
/* upper-right */ /* upper-right */
Vec2 v3; Vec2Packed v3;
Vec2 uv3; Vec2Packed uv3;
Color c3; ColorPacked c3;
} ElementIndexedQuad; } ElementIndexedQuad;
typedef struct ElementIndexedQuadWithoutColor { typedef struct ElementIndexedQuadWithoutColor {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2Packed v0;
Vec2 uv0; Vec2Packed uv0;
/* bottom-left */ /* bottom-left */
Vec2 v1; Vec2Packed v1;
Vec2 uv1; Vec2Packed uv1;
/* bottom-right */ /* bottom-right */
Vec2 v2; Vec2Packed v2;
Vec2 uv2; Vec2Packed uv2;
/* upper-right */ /* upper-right */
Vec2 v3; Vec2Packed v3;
Vec2 uv3; Vec2Packed uv3;
} ElementIndexedQuadWithoutColor; } ElementIndexedQuadWithoutColor;
typedef struct ElementIndexedQuadWithoutTexture { typedef struct ElementIndexedQuadWithoutTexture {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2Packed v0;
Color c0; ColorPacked c0;
/* bottom-left */ /* bottom-left */
Vec2 v1; Vec2Packed v1;
Color c1; ColorPacked c1;
/* bottom-right */ /* bottom-right */
Vec2 v2; Vec2Packed v2;
Color c2; ColorPacked c2;
/* upper-right */ /* upper-right */
Vec2 v3; Vec2Packed v3;
Color c3; ColorPacked c3;
} ElementIndexedQuadWithoutTexture; } ElementIndexedQuadWithoutTexture;
typedef struct ElementIndexedQuadWithoutColorWithoutTexture { typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2Packed v0;
/* bottom-left */ /* bottom-left */
Vec2 v1; Vec2Packed v1;
/* bottom-right */ /* bottom-right */
Vec2 v2; Vec2Packed v2;
/* upper-right */ /* upper-right */
Vec2 v3; Vec2Packed v3;
} ElementIndexedQuadWithoutColorWithoutTexture; } 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,
@ -123,266 +102,13 @@ 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 */ #define CIRCLE_VERTICES_MAX 2048
/* 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;
@ -415,22 +141,11 @@ static void finally_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();
@ -467,25 +182,45 @@ static void finally_use_2d_pipeline(void) {
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();
texture_mode_last_used = -1;
pipeline_last_used = PIPELINE_2D; pipeline_last_used = PIPELINE_2D;
} }
void use_texture_mode(TextureMode mode) { void render_circle(const CirclePrimitive *circle) {
DeferredCommand const command = { static SDL_Vertex vertices[CIRCLE_VERTICES_MAX];
.type = DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE, static int indices[CIRCLE_VERTICES_MAX * 3];
.use_texture_mode = { mode } int num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX-1);
};
arrpush(deferred_commands, command); 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);
} }
static void finally_use_texture_mode(TextureMode mode) { void 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);
@ -524,8 +259,6 @@ static void finally_use_texture_mode(TextureMode mode) {
} else { } else {
glCallList(lists + 2); glCallList(lists + 2);
} }
texture_mode_last_used = mode;
} }
@ -549,7 +282,7 @@ bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
if (builder->bytes_left == 0) if (builder->bytes_left == 0)
return false; return false;
memcpy(builder->mapping, bytes, size); SDL_memcpy(builder->mapping, bytes, size);
builder->bytes_left -= size; builder->bytes_left -= size;
/* trigger data send */ /* trigger data send */
@ -568,7 +301,7 @@ void finally_render_quads(const Primitive2D primitives[],
const struct QuadBatch batch, const struct QuadBatch batch,
const VertexBuffer buffer) const VertexBuffer buffer)
{ {
DeferredCommandDraw command = {0}; (void)buffer;
GLsizei off = 0, voff = 0, uvoff = 0, coff = 0; GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
@ -590,55 +323,63 @@ void finally_render_quads(const Primitive2D primitives[],
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0); voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
} }
command.vertices = (AttributeArrayPointer) { /* vertex specification */
.arity = 2, glEnableClientState(GL_VERTEX_ARRAY);
.type = GL_FLOAT, glVertexPointer(2,
.stride = off, GL_FLOAT,
.offset = voff, off,
.buffer = buffer (void *)(size_t)voff);
};
if (batch.textured)
command.texcoords = (AttributeArrayPointer) {
.arity = 2,
.type = GL_FLOAT,
.stride = off,
.offset = uvoff,
.buffer = buffer
};
if (!batch.constant_colored) {
command.colors = (AttributeArrayPointer) {
.arity = 4,
.type = GL_UNSIGNED_BYTE,
.stride = off,
.offset = coff,
.buffer = buffer
};
} else {
command.constant_colored = true;
command.color = primitives[0].sprite.color;
}
if (batch.textured) { if (batch.textured) {
/* TODO: bad, don't */ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
command.textured = true; glClientActiveTexture(GL_TEXTURE0);
command.texture_key = primitives->sprite.texture_key; glTexCoordPointer(2,
command.texture_repeat = batch.repeat; GL_FLOAT,
off,
(void *)(size_t)uvoff);
} }
command.element_buffer = get_quad_element_buffer(); if (!batch.constant_colored) {
command.element_count = 6 * (GLsizei)batch.size; glEnableClientState(GL_COLOR_ARRAY);
command.range_end = 6 * (GLsizei)batch.size; glColorPointer(4,
GL_UNSIGNED_BYTE,
off,
(void *)(size_t)coff);
} else
glColor4ub(primitives[0].sprite.color.r,
primitives[0].sprite.color.g,
primitives[0].sprite.color.b,
primitives[0].sprite.color.a);
use_texture_mode(batch.mode); if (batch.textured) {
if (!batch.repeat)
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key);
else
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key);
}
DeferredCommand final_command = { bind_quad_element_buffer();
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
arrpush(deferred_commands, final_command); glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, GL_UNSIGNED_SHORT, NULL);
/* clear the state */
{
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
if (batch.textured)
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
if (!batch.constant_colored)
glDisableClientState(GL_COLOR_ARRAY);
if (batch.textured)
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
} }
@ -665,62 +406,62 @@ bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
{ {
if (!batch.constant_colored && batch.textured) { if (!batch.constant_colored && batch.textured) {
ElementIndexedQuad const buffer_element = { ElementIndexedQuad const buffer_element = {
.v0 = v0, .v0 = m_vec2_packed_from(v0),
.v1 = v1, .v1 = m_vec2_packed_from(v1),
.v2 = v2, .v2 = m_vec2_packed_from(v2),
.v3 = v3, .v3 = m_vec2_packed_from(v3),
.uv0 = uv0, .uv0 = m_vec2_packed_from(uv0),
.uv1 = uv1, .uv1 = m_vec2_packed_from(uv1),
.uv2 = uv2, .uv2 = m_vec2_packed_from(uv2),
.uv3 = uv3, .uv3 = m_vec2_packed_from(uv3),
/* equal for all (flat shaded) */ /* equal for all (flat shaded) */
.c0 = color, .c0 = m_color_packed_from(color),
// .c1 = color, // .c1 = m_color_packed_from(color),
.c2 = color, .c2 = m_color_packed_from(color),
// .c3 = color, // .c3 = m_color_packed_from(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 if (batch.constant_colored && batch.textured) { } else if (batch.constant_colored && batch.textured) {
ElementIndexedQuadWithoutColor const buffer_element = { ElementIndexedQuadWithoutColor const buffer_element = {
.v0 = v0, .v0 = m_vec2_packed_from(v0),
.v1 = v1, .v1 = m_vec2_packed_from(v1),
.v2 = v2, .v2 = m_vec2_packed_from(v2),
.v3 = v3, .v3 = m_vec2_packed_from(v3),
.uv0 = uv0, .uv0 = m_vec2_packed_from(uv0),
.uv1 = uv1, .uv1 = m_vec2_packed_from(uv1),
.uv2 = uv2, .uv2 = m_vec2_packed_from(uv2),
.uv3 = uv3, .uv3 = m_vec2_packed_from(uv3),
}; };
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 if (!batch.constant_colored && !batch.textured) { } else if (!batch.constant_colored && !batch.textured) {
ElementIndexedQuadWithoutTexture const buffer_element = { ElementIndexedQuadWithoutTexture const buffer_element = {
.v0 = v0, .v0 = m_vec2_packed_from(v0),
.v1 = v1, .v1 = m_vec2_packed_from(v1),
.v2 = v2, .v2 = m_vec2_packed_from(v2),
.v3 = v3, .v3 = m_vec2_packed_from(v3),
/* equal for all (flat shaded) */ /* equal for all (flat shaded) */
.c0 = color, .c0 = m_color_packed_from(color),
// .c1 = color, // .c1 = m_color_packed_from(color),
.c2 = color, .c2 = m_color_packed_from(color),
// .c3 = color, // .c3 = m_color_packed_from(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 if (batch.constant_colored && !batch.textured) { } else if (batch.constant_colored && !batch.textured) {
ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = { ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = {
.v0 = v0, .v0 = m_vec2_packed_from(v0),
.v1 = v1, .v1 = m_vec2_packed_from(v1),
.v2 = v2, .v2 = m_vec2_packed_from(v2),
.v3 = v3, .v3 = m_vec2_packed_from(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);
@ -776,15 +517,15 @@ bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data,
(void)font_data; (void)font_data;
ElementIndexedQuadWithoutColor buffer_element = { ElementIndexedQuadWithoutColor buffer_element = {
.v0 = (Vec2){ quad.x0, quad.y0 }, .v0 = (Vec2Packed){ quad.x0, quad.y0 },
.v1 = (Vec2){ quad.x1, quad.y0 }, .v1 = (Vec2Packed){ quad.x1, quad.y0 },
.v2 = (Vec2){ quad.x1, quad.y1 }, .v2 = (Vec2Packed){ quad.x1, quad.y1 },
.v3 = (Vec2){ quad.x0, quad.y1 }, .v3 = (Vec2Packed){ quad.x0, quad.y1 },
.uv0 = (Vec2){ quad.s0, quad.t0 }, .uv0 = (Vec2Packed){ quad.s0, quad.t0 },
.uv1 = (Vec2){ quad.s1, quad.t0 }, .uv1 = (Vec2Packed){ quad.s1, quad.t0 },
.uv2 = (Vec2){ quad.s1, quad.t1 }, .uv2 = (Vec2Packed){ quad.s1, quad.t1 },
.uv3 = (Vec2){ quad.s0, quad.t1 }, .uv3 = (Vec2Packed){ quad.s0, quad.t1 },
}; };
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
@ -796,46 +537,44 @@ void finally_draw_text(FontData const *font_data,
Color color, Color color,
VertexBuffer buffer) VertexBuffer buffer)
{ {
DeferredCommandDraw command = {0}; (void)buffer;
command.vertices = (AttributeArrayPointer) { /* vertex specification */
.arity = 2, glEnableClientState(GL_VERTEX_ARRAY);
.type = GL_FLOAT, glVertexPointer(2,
.stride = offsetof(ElementIndexedQuadWithoutColor, v1), GL_FLOAT,
.offset = offsetof(ElementIndexedQuadWithoutColor, v0), offsetof(ElementIndexedQuadWithoutColor, v1),
.buffer = buffer (void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, v0));
};
command.texcoords = (AttributeArrayPointer) { glEnableClientState(GL_TEXTURE_COORD_ARRAY);
.arity = 2, glClientActiveTexture(GL_TEXTURE0);
.type = GL_FLOAT, glTexCoordPointer(2,
.stride = offsetof(ElementIndexedQuadWithoutColor, v1), GL_FLOAT,
.offset = offsetof(ElementIndexedQuadWithoutColor, uv0), offsetof(ElementIndexedQuadWithoutColor, v1),
.buffer = buffer (void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0));
};
command.constant_colored = true; bind_quad_element_buffer();
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);
DeferredCommand final_command = { glBindTexture(GL_TEXTURE_2D, font_data->texture);
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
arrpush(deferred_commands, final_command); glColor4ub(color.r, color.g, color.b, color.a);
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);
} }
@ -864,47 +603,6 @@ 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;
@ -1071,16 +769,3 @@ 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);
}

View File

@ -32,60 +32,65 @@ void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes)
} }
VertexBuffer get_quad_element_buffer(void) { void bind_quad_element_buffer(void) {
static VertexBuffer buffer = 0; static GLuint 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) {
buffer = create_vertex_buffer(); glGenBuffers(1, &buffer);
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 ); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
QUAD_ELEMENT_BUFFER_LENGTH * 6 * sizeof(uint16_t),
NULL,
GL_STATIC_DRAW);
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) { uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER,
GLshort indices[6]; GL_WRITE_ONLY);
indices[0] = (GLshort)(i * 4 + 0); if (!indices)
indices[1] = (GLshort)(i * 4 + 1); CRY("Quad indices generation", "glMapBuffer() failed");
indices[2] = (GLshort)(i * 4 + 2);
indices[3] = (GLshort)(i * 4 + 2);
indices[4] = (GLshort)(i * 4 + 3);
indices[5] = (GLshort)(i * 4 + 0);
push_to_vertex_buffer_builder(&builder, indices, sizeof indices); for (uint16_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
} 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);
} }
SDL_assert_always(buffer); glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
return buffer; } else
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
} }
VertexBuffer get_circle_element_buffer(void) { void clear_draw_buffer(void) {
static VertexBuffer buffer = 0; /* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
if (buffer == 0) { glDepthRange(0.0, 1.0);
buffer = create_vertex_buffer(); glDepthMask(GL_TRUE);
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * CIRCLE_ELEMENT_BUFFER_LENGTH);
for (size_t i = 1; i < CIRCLE_VERTICES_MAX; ++i) { /* TODO: don't clear color when skybox is applied? */
/* first one is center point index, always zero */ /* for that window should match framebuffer */
GLshort indices[3]; /* also it is driver dependent, from what i can gather */
/* INFO: also, based on below, driver might prefer it staying this way */
indices[0] = 0; /* https://gamedev.stackexchange.com/questions/90344/render-with-const-depth-value */
/* we could optionally load ARB_invalidate_subdata extension if it's available instead */
/* generated point index */ glClear(GL_COLOR_BUFFER_BIT |
indices[1] = (GLshort)i; GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
size_t index = (i + 1) % (CIRCLE_VERTICES_MAX - 1);
if (index == 0) /* don't use center for outer ring */
index = (CIRCLE_VERTICES_MAX - 1);
indices[2] = (GLshort)index;
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
}
} }
SDL_assert_always(buffer);
return buffer; void swap_buffers(void) {
SDL_GL_SwapWindow(ctx.window);
}
void set_depth_range(double low, double high) {
glDepthRange(low, high);
} }

View File

@ -72,7 +72,9 @@ void render_rect_batch(const Primitive2D primitives[],
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT); SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */ /* single vertex array is used for every batch with NULL glBufferData() trick at the end */
VertexBuffer const vertex_array = get_scratch_vertex_array(); static VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode); use_texture_mode(batch.mode);

View File

@ -125,15 +125,16 @@ void render_sprite_batch(const Primitive2D primitives[],
SDL_assert(primitives && batch.size != 0); SDL_assert(primitives && batch.size != 0);
SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE); SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
VertexBuffer const vertex_array = get_scratch_vertex_array(); /* single vertex array is used for every batch with NULL glBufferData() trick at the end */
static VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode);
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_quad_payload_size(batch) * batch.size); VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
@ -143,16 +144,13 @@ void render_sprite_batch(const Primitive2D primitives[],
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;
if (primitives[cur].sprite.texture_key.id != cached_srcrect_key.id) { /* TODO: try caching it */
cached_srcrect = textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key); const Rect srcrect =
cached_srcrect_key = primitives[cur].sprite.texture_key; textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
}
Rect const srcrect = cached_srcrect;
Vec2 uv0, uv1, uv2, uv3; Vec2 uv0, uv1, uv2, uv3;
if (!batch.repeat) { if (!sprite.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;

View File

@ -171,7 +171,9 @@ static void text_destroy_font_data(FontData *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 const vertex_array = get_scratch_vertex_array(); VertexBuffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t len = SDL_strlen(text); const size_t len = SDL_strlen(text);

View File

@ -45,7 +45,9 @@ 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)
{ {
VertexBuffer const vertex_array = get_scratch_vertex_array(); static VertexBuffer vertex_array = 0;
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);

View File

@ -77,7 +77,6 @@ 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? */

View File

@ -658,8 +658,6 @@ static bool initialize(void) {
} }
*/ */
ctx.render_double_buffered = true;
return true; return true;
fail: fail:

View File

@ -50,7 +50,6 @@ 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);

View File

@ -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 = sinf(a); const float s = SDL_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