501 lines
13 KiB
C
501 lines
13 KiB
C
#include "render.h"
|
|
#include "assets.h"
|
|
#include "assert.h"
|
|
#include "layers.h"
|
|
#include "signal.h"
|
|
#include "inttypes.h"
|
|
#include "string.h"
|
|
#include "SDL2/SDL_blendmode.h"
|
|
#include "SDL2/SDL_render.h"
|
|
#include "SDL2/SDL_surface.h"
|
|
#include "SDL2/SDL_ttf.h"
|
|
#include <stddef.h>
|
|
|
|
#define NUM_DRAWCMDS 2048
|
|
|
|
drawcmd_t g_drawdata[NUM_DRAWCMDS];
|
|
drawcmd_t* g_drawdata_endptr = g_drawdata;
|
|
int _render_mode = 0;
|
|
|
|
#define DEFAULT_VIEW (view_t){.x=0.0,.y=0.0,.width=1.0}
|
|
|
|
float _aspect_ratio;
|
|
float _render_width=0, _render_height=0;
|
|
|
|
view_t g_active_view = DEFAULT_VIEW;
|
|
|
|
int d_debug_next_frame = 0;
|
|
|
|
void screen_to_view(float *x, float *y) {
|
|
float xx = *x, yy = *y;
|
|
|
|
xx *= g_active_view.width;
|
|
yy *= g_active_view.width;
|
|
|
|
xx += g_active_view.x - g_active_view.width * 0.5f;
|
|
yy += g_active_view.y - g_active_view.width * _aspect_ratio * 0.5f;
|
|
|
|
*x = xx;
|
|
*y = yy;
|
|
}
|
|
|
|
void clear_buffer() {
|
|
SDL_SetRenderDrawBlendMode(g_context.renderer, SDL_BLENDMODE_BLEND);
|
|
SDL_SetRenderDrawColor(g_context.renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(g_context.renderer);
|
|
}
|
|
|
|
void get_scaling_factors(float* width, float* height,
|
|
float* width_mul, float* height_mul, short ui) {
|
|
float aspectr;
|
|
(*width)=_render_width; (*height)=_render_height;
|
|
aspectr = _aspect_ratio;
|
|
if(ui == 0) {
|
|
(*width_mul) = (*width)/g_active_view.width;
|
|
(*height_mul) = (*height)/(g_active_view.width*aspectr);
|
|
} else {
|
|
(*height_mul) = (*width_mul) = (*width);
|
|
}
|
|
(*width) = g_active_view.width;
|
|
(*height) = (*width)*aspectr;
|
|
}
|
|
|
|
SDL_FRect get_dest_with_size(SDL_FRect untransformed, int ui) {
|
|
float fw, fh, fwm, fhm;
|
|
get_scaling_factors(&fw, &fh, &fwm, &fhm, ui);
|
|
SDL_FRect r = (SDL_FRect) {
|
|
.x=((-g_active_view.x)+(fw*0.5f)+untransformed.x)*fwm,
|
|
.y=((-g_active_view.y)+(fh*0.5f)+untransformed.y)*fwm,
|
|
.w=untransformed.w*fwm,
|
|
.h=untransformed.h*fwm
|
|
};
|
|
if(ui) {
|
|
r.x = untransformed.x * fwm;
|
|
r.y = untransformed.y * fhm;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static
|
|
void _exec_sprite_cmd(const drawcmd_t* cmd) {
|
|
const sprite_t* sprite = &cmd->sprite;
|
|
SDL_FRect untransformed = {sprite->x, sprite->y, sprite->sx, sprite->sy};
|
|
untransformed.x -= sprite->origin.x;
|
|
untransformed.y -= sprite->origin.y;
|
|
SDL_FRect destrect = get_dest_with_size(untransformed, cmd->ui);
|
|
SDL_FPoint origin = {destrect.w * sprite->origin.x, destrect.h * sprite->origin.y};
|
|
SDL_RenderCopyExF(g_context.renderer, sprite->texture,
|
|
&sprite->uv, &destrect, sprite->rot,
|
|
&origin, sprite->flip);
|
|
}
|
|
|
|
static
|
|
void _exec_rect_cmd(const drawcmd_t* cmd) {
|
|
float w, h, wm, hm;
|
|
get_scaling_factors(&w, &h, &wm, &hm, cmd->ui);
|
|
SDL_FRect rect = (SDL_FRect) {
|
|
.x=((-g_active_view.x)+(w*0.5f)+cmd->rect.x)*wm,
|
|
.y=((-g_active_view.y)+(h*0.5f)+cmd->rect.y)*hm,
|
|
.w=cmd->rect.w*wm, .h=cmd->rect.h*hm
|
|
};
|
|
if(cmd->ui) {
|
|
rect.x = cmd->rect.x * wm;
|
|
rect.y = cmd->rect.y * hm;
|
|
}
|
|
SDL_Color c = cmd->rect.background;
|
|
SDL_SetRenderDrawColor(g_context.renderer, c.r, c.g, c.b, c.a);
|
|
SDL_RenderFillRectF(g_context.renderer, &rect);
|
|
c = cmd->rect.line;
|
|
SDL_SetRenderDrawColor(g_context.renderer, c.r, c.g, c.b, c.a);
|
|
|
|
SDL_FRect r = {
|
|
rect.x, rect.y,
|
|
cmd->rect.line_width*wm, rect.h
|
|
};
|
|
SDL_RenderFillRectF(g_context.renderer, &r);
|
|
r = (SDL_FRect){
|
|
rect.x, rect.y,
|
|
rect.w, cmd->rect.line_width*hm
|
|
};
|
|
SDL_RenderFillRectF(g_context.renderer, &r);
|
|
r = (SDL_FRect){
|
|
rect.x, rect.y+rect.h-cmd->rect.line_width*hm,
|
|
rect.w, cmd->rect.line_width*hm
|
|
};
|
|
SDL_RenderFillRectF(g_context.renderer, &r);
|
|
r = (SDL_FRect){
|
|
rect.x+rect.w-cmd->rect.line_width*wm, rect.y,
|
|
cmd->rect.line_width*wm, rect.h
|
|
};
|
|
SDL_RenderFillRectF(g_context.renderer, &r);
|
|
}
|
|
|
|
static
|
|
void _exec_sliced_cmd(const drawcmd_t* cmd) {
|
|
const nineslice_t* sliced = &cmd->sliced;
|
|
|
|
// target rect in world space
|
|
SDL_FRect rect = sliced->rect;
|
|
// sliced texture
|
|
SDL_Texture* t = sliced->texture;
|
|
|
|
// width and height of sliced texture
|
|
int tw, th;
|
|
SDL_QueryTexture(t, NULL, NULL, &tw, &th);
|
|
|
|
// top-left
|
|
SDL_Rect srcr = {
|
|
0, 0, sliced->corner_size, sliced->corner_size
|
|
};
|
|
SDL_FRect dstr = get_dest_with_size((SDL_FRect){
|
|
rect.x, rect.y, sliced->radius, sliced->radius
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// top - centre
|
|
srcr = (SDL_Rect) {
|
|
sliced->corner_size, 0,
|
|
tw - sliced->corner_size*2, sliced->corner_size
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect){
|
|
rect.x + sliced->radius, rect.y, sliced->rect.w - sliced->radius * 2, sliced->radius
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// top-right
|
|
srcr = (SDL_Rect) {
|
|
tw - sliced->corner_size, 0, sliced->corner_size, sliced->corner_size
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect){
|
|
rect.x + rect.w - sliced->radius, rect.y, sliced->radius, sliced->radius
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// centre-left
|
|
srcr = (SDL_Rect) {
|
|
0, sliced->corner_size, sliced->corner_size, th - sliced->corner_size * 2
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect) {
|
|
rect.x, rect.y + sliced->radius, sliced->radius, rect.h - sliced->radius * 2
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// centre-centre
|
|
srcr = (SDL_Rect) {
|
|
sliced->corner_size, sliced->corner_size, tw - sliced->corner_size * 2, th - sliced->corner_size * 2
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect) {
|
|
rect.x + sliced->radius, rect.y + sliced->radius, rect.w - sliced->radius * 2, rect.h - sliced->radius * 2
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// centre-right
|
|
srcr = (SDL_Rect) {
|
|
tw - sliced->corner_size, sliced->corner_size, sliced->corner_size, th - sliced->corner_size * 2
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect) {
|
|
rect.x + rect.w - sliced->radius, rect.y + sliced->radius, sliced->radius, rect.h - sliced->radius * 2
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// bottom-left
|
|
srcr = (SDL_Rect) {
|
|
0, th - sliced->corner_size, sliced->corner_size, sliced->corner_size
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect){
|
|
rect.x, rect.y + rect.h - sliced->radius, sliced->radius, sliced->radius
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// bottom-centre
|
|
srcr = (SDL_Rect) {
|
|
sliced->corner_size, th - sliced->corner_size, tw - sliced->corner_size * 2, sliced->corner_size
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect) {
|
|
rect.x + sliced->radius, rect.y + rect.h - sliced->radius, rect.w - sliced->radius * 2, sliced->radius
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
|
|
// bottom-right
|
|
srcr = (SDL_Rect) {
|
|
tw - sliced->corner_size, th - sliced->corner_size, sliced->corner_size, sliced->corner_size
|
|
};
|
|
dstr = get_dest_with_size((SDL_FRect) {
|
|
rect.x + rect.w - sliced->radius, rect.y + rect.h - sliced->radius, sliced->radius, sliced->radius
|
|
}, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &dstr);
|
|
}
|
|
|
|
static
|
|
void _exec_text_cmd(const drawcmd_t* cmd) {
|
|
SDL_FRect r = cmd->text.area;
|
|
int fh = TTF_FontHeight(cmd->text.style.font);
|
|
int wrap = (int)(fh * r.w / cmd->text.style.size);
|
|
SDL_Surface* s = TTF_RenderText_Solid_Wrapped(cmd->text.style.font, cmd->text.text, cmd->text.style.color, wrap);
|
|
if(s != NULL) {
|
|
SDL_Rect srcr = {0,0,s->w, s->h};
|
|
SDL_Texture* t = SDL_CreateTextureFromSurface(g_context.renderer, s);
|
|
SDL_FreeSurface(s);
|
|
|
|
float asp_dst = r.w / r.h;
|
|
float asp_src = (float)srcr.w / (float)srcr.h;
|
|
if((float)s->h / fh * cmd->text.style.size > r.h) {
|
|
srcr.h = srcr.w / asp_dst;
|
|
}
|
|
|
|
r.w = (float)srcr.w / fh * cmd->text.style.size;
|
|
r.h = (float)srcr.h / fh * cmd->text.style.size;
|
|
|
|
r = get_dest_with_size(r, cmd->ui);
|
|
SDL_RenderCopyF(g_context.renderer, t, &srcr, &r);
|
|
SDL_DestroyTexture(t);
|
|
}
|
|
free(cmd->text.text);
|
|
}
|
|
|
|
sprite_t sprite_default() {
|
|
return (sprite_t){
|
|
.texture = NULL,
|
|
.x = 0.f, .y = 0.f,
|
|
.origin = (SDL_FPoint){0.f, 0.f},
|
|
.sx = 1.f, .sy = 1.f,
|
|
.rot = 0.f,
|
|
.depth = RLAYER_SPRITES,
|
|
.uv = (SDL_Rect){0.f, 0.f, 0.f, 0.f},
|
|
.flip = SDL_FLIP_NONE,
|
|
};
|
|
}
|
|
|
|
sprite_t render_text(const char* str, SDL_FRect area, text_style_t style) {
|
|
SDL_FRect r = area;
|
|
int fh = TTF_FontHeight(style.font);
|
|
int wrap = (int)(fh * r.w / style.size);
|
|
SDL_Surface* s = TTF_RenderText_Solid_Wrapped(style.font, str, style.color, wrap);
|
|
if(s != NULL) {
|
|
SDL_Rect srcr = {0,0,s->w, s->h};
|
|
SDL_Texture* t = SDL_CreateTextureFromSurface(g_context.renderer, s);
|
|
SDL_FreeSurface(s);
|
|
|
|
float asp_dst = r.w / r.h;
|
|
float asp_src = (float)srcr.w / (float)srcr.h;
|
|
if((float)s->h / fh * style.size > r.h) {
|
|
srcr.h = srcr.w / asp_dst;
|
|
}
|
|
|
|
r.w = (float)srcr.w / fh * style.size;
|
|
r.h = (float)srcr.h / fh * style.size;
|
|
|
|
return (sprite_t) {
|
|
.depth=RLAYER_SPRITES,
|
|
.origin={0,0},
|
|
.rot=0,
|
|
.sx=area.w, .sy=area.h,
|
|
.x=area.x, .y=area.y,
|
|
.texture=t,
|
|
.uv=srcr,
|
|
.flip=SDL_FLIP_NONE,
|
|
};
|
|
}
|
|
return (sprite_t) {
|
|
.depth=0,
|
|
.origin={0,0}, .rot=0,
|
|
.sx=0, .sy=0,
|
|
.texture=NULL,
|
|
.uv={0,0,0,0},
|
|
.x=0,.y=0,
|
|
.flip=SDL_FLIP_NONE,
|
|
};
|
|
}
|
|
|
|
typedef void(*drawcmd_delegate)(const drawcmd_t*);
|
|
static drawcmd_delegate const drawcmd_funcs[] = {
|
|
&_exec_sprite_cmd,
|
|
&_exec_rect_cmd,
|
|
&_exec_sliced_cmd,
|
|
&_exec_text_cmd,
|
|
};
|
|
|
|
static inline
|
|
void _exec_buffer() {
|
|
if(d_debug_next_frame) printf("debug capture of draw buffer\ncount: %zu\n", (size_t)(g_drawdata_endptr - g_drawdata));
|
|
for(const drawcmd_t* cmd = g_drawdata; cmd != g_drawdata_endptr; ++cmd) {
|
|
if(cmd->type > DRAWCMDTYPE_MIN && cmd->type < DRAWCMDTYPE_MAX) {
|
|
if(d_debug_next_frame) {
|
|
printf("depth: %d, type: %d\n", cmd->depth, cmd->type);
|
|
}
|
|
drawcmd_funcs[cmd->type](cmd);
|
|
}
|
|
}
|
|
}
|
|
|
|
void swap_buffer() {
|
|
clear_buffer();
|
|
int iw, ih;
|
|
SDL_GetRendererOutputSize(g_context.renderer, &iw, &ih);
|
|
_render_width = (float)iw; _render_height = (float)ih;
|
|
_aspect_ratio = _render_height/_render_width;
|
|
_exec_buffer();
|
|
SDL_RenderPresent(g_context.renderer);
|
|
g_drawdata_endptr = g_drawdata;
|
|
}
|
|
|
|
static inline
|
|
void _insert_drawcmd_at(size_t index, const drawcmd_t* cmd) {
|
|
drawcmd_t* insertpoint = g_drawdata + index;
|
|
drawcmd_t* dest = insertpoint + 1;
|
|
size_t size = (size_t)(g_drawdata_endptr - g_drawdata);
|
|
++g_drawdata_endptr;
|
|
if(size > index) {
|
|
size_t count = (size - index);
|
|
if(size > 0)
|
|
{
|
|
memmove(dest, insertpoint, count*sizeof(drawcmd_t));
|
|
}
|
|
}
|
|
*insertpoint = *cmd;
|
|
insertpoint->ui = _render_mode == 1;
|
|
}
|
|
|
|
static inline
|
|
void _draw(const drawcmd_t* cmd) {
|
|
if(g_drawdata_endptr == g_drawdata) {
|
|
_insert_drawcmd_at(0, cmd);
|
|
return;
|
|
}
|
|
long top = (size_t)(g_drawdata_endptr - g_drawdata),
|
|
bot = 0,
|
|
med = 0;
|
|
|
|
if(top != bot) {
|
|
while(bot <= top) {
|
|
med = floor((float)(top + bot) / 2);
|
|
if(g_drawdata[med].depth > cmd->depth) {
|
|
bot = med+1;
|
|
} else if(g_drawdata[med].depth < cmd->depth) {
|
|
top = med-1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
size_t count = (g_drawdata_endptr - g_drawdata);
|
|
int diff = g_drawdata[med].depth - cmd->depth;
|
|
while(diff > 0 && med < count) {
|
|
med++;
|
|
diff = g_drawdata[med].depth - cmd->depth;
|
|
}
|
|
_insert_drawcmd_at(med, cmd);
|
|
}
|
|
|
|
void draw_sprite(const sprite_t* sprite) {
|
|
drawcmd_t d = {
|
|
.type=DRAWCMDTYPE_SPRITE,
|
|
.depth=sprite->depth,
|
|
.sprite=*sprite
|
|
};
|
|
_draw(&d);
|
|
}
|
|
|
|
void draw_rect(const rectshape_t* rect) {
|
|
drawcmd_t d = {
|
|
.type=DRAWCMDTYPE_RECT,
|
|
.depth=rect->depth,
|
|
.rect=*rect
|
|
};
|
|
_draw(&d);
|
|
}
|
|
|
|
void draw_sliced(const nineslice_t *sliced) {
|
|
drawcmd_t d = {
|
|
.type=DRAWCMDTYPE_SLICED,
|
|
.depth=sliced->depth,
|
|
.sliced=*sliced
|
|
};
|
|
_draw(&d);
|
|
}
|
|
|
|
void draw_text(const char *str, SDL_FRect area, text_style_t style, depth_t depth) {
|
|
int len = strlen(str);
|
|
textarea_t t = {
|
|
.text = calloc(len+1, sizeof(char)),
|
|
.area = area,
|
|
.style = style,
|
|
};
|
|
strcpy(t.text, str);
|
|
drawcmd_t d = {
|
|
.type=DRAWCMDTYPE_TEXT,
|
|
.text=t,
|
|
.depth=depth,
|
|
};
|
|
_draw(&d);
|
|
}
|
|
|
|
spritesheet_t make_spritesheet(const char *file, int tiles_x, int tiles_y) {
|
|
spritesheet_t sheet=(spritesheet_t){
|
|
.texture=get_texture(file),
|
|
.w=0,.h=0,
|
|
};
|
|
SDL_QueryTexture(sheet.texture, NULL, NULL, &sheet.w, &sheet.h);
|
|
sheet.tile_width = sheet.w / tiles_x;
|
|
sheet.tile_height = sheet.h / tiles_y;
|
|
return sheet;
|
|
}
|
|
|
|
nineslice_t make_nineslice(const char *file, int corner_px, float radius) {
|
|
nineslice_t sliced = {
|
|
.depth = RLAYER_UI,
|
|
.corner_size = corner_px,
|
|
.radius = radius,
|
|
.rect= {0.0, 0.0, 1.0, 1.0},
|
|
.texture=get_texture(file)
|
|
};
|
|
return sliced;
|
|
}
|
|
|
|
sprite_t make_sprite(const char* file, float x, float y) {
|
|
sprite_t sprite=(sprite_t){
|
|
.texture=get_texture(file),
|
|
.x=x,.y=y,
|
|
.origin=(SDL_FPoint){.x=0.0,.y=0.0},
|
|
.sx=1.0,.sy=1.0,
|
|
.rot=0,
|
|
.depth=RLAYER_SPRITES,
|
|
.uv=(SDL_Rect){0,0,0,0},
|
|
.flip=SDL_FLIP_NONE,
|
|
};
|
|
SDL_QueryTexture(sprite.texture, NULL, NULL, &sprite.uv.w, &sprite.uv.h);
|
|
return sprite;
|
|
}
|
|
|
|
sprite_t sprite_from_spritesheet(spritesheet_t *sheet, int index) {
|
|
SDL_Rect rect = get_srcrect_from(sheet, index);
|
|
return (sprite_t) {
|
|
.texture=sheet->texture,
|
|
.x=0, .y=0,
|
|
.origin=(SDL_FPoint){0,0},
|
|
.sx=1.0, .sy=1.0,
|
|
.rot=0,
|
|
.depth=RLAYER_SPRITES,
|
|
.uv=rect,
|
|
.flip=SDL_FLIP_NONE,
|
|
};
|
|
}
|
|
|
|
text_style_t make_text_style(const char *font, SDL_Color color, int dpi, float size) {
|
|
TTF_Font* fnt = get_font(font, dpi);
|
|
return (text_style_t){
|
|
.font = fnt,
|
|
.size = size,
|
|
.color = color
|
|
};
|
|
}
|
|
|
|
SDL_Rect get_srcrect_from(spritesheet_t *sheet, int index) {
|
|
int pixels = index * sheet->tile_width;
|
|
int w = sheet->w / sheet->tile_width;
|
|
return (SDL_Rect) {
|
|
pixels%sheet->w, index/w * sheet->tile_height,
|
|
sheet->tile_width, sheet->tile_height,
|
|
};
|
|
}
|