mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
Multi-line and tabbed strings drawn with a scaleMultiplier != 1 overlapped or misaligned because the font code advanced newlines and tabs by the unscaled font height/tab width -- most visible in FRED's viewport labels now that they honor an adjustable font scale. Scale the newline and tab advance in both the rendering and measurement paths of each backend: the NVG path (gr_string and NVGFont::getStringSize) and the VFNT path (gr_string_old and VFNTFont::getStringSize), which also gains tab handling. The tab width is hoisted onto the shared FSFont base so both backends share it. While here, pass the FSFont wrapper to gr_string_old instead of a bag of scalars, make its font pointer const, and consolidate the get_char_width_old externs into a single declaration in font_internal.h. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
1494 lines
37 KiB
C++
1494 lines
37 KiB
C++
|
|
#include "graphics/render.h"
|
|
|
|
#include "graphics/material.h"
|
|
#include "graphics/matrix.h"
|
|
#include "graphics/paths/PathRenderer.h"
|
|
#include "graphics/software/FSFont.h"
|
|
#include "graphics/software/NVGFont.h"
|
|
#include "graphics/software/VFNTFont.h"
|
|
#include "graphics/software/font_internal.h"
|
|
#include "localization/localize.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "render/3d.h"
|
|
|
|
static void gr_flash_internal(int r, int g, int b, int a, bool alpha_flash)
|
|
{
|
|
CLAMP(r, 0, 255);
|
|
CLAMP(g, 0, 255);
|
|
CLAMP(b, 0, 255);
|
|
CLAMP(a, 0, 255);
|
|
|
|
int x1 = (gr_screen.clip_left + gr_screen.offset_x);
|
|
int y1 = (gr_screen.clip_top + gr_screen.offset_y);
|
|
int x2 = (gr_screen.clip_right + gr_screen.offset_x) + 1;
|
|
int y2 = (gr_screen.clip_bottom + gr_screen.offset_y) + 1;
|
|
|
|
material render_material;
|
|
// By default materials use the immediate mode shader which is fine for us
|
|
render_material.set_depth_mode(ZBUFFER_TYPE_NONE);
|
|
|
|
if (alpha_flash) {
|
|
render_material.set_color(r, g, b, a);
|
|
render_material.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
|
|
} else {
|
|
render_material.set_color(r, g, b, 255);
|
|
render_material.set_blend_mode(ALPHA_BLEND_ALPHA_ADDITIVE);
|
|
}
|
|
|
|
float glVertices[8] = { (float)x1, (float)y1, (float)x1, (float)y2,
|
|
(float)x2, (float)y1, (float)x2, (float)y2 };
|
|
|
|
vertex_layout vert_def;
|
|
|
|
vert_def.add_vertex_component(vertex_format_data::POSITION2, sizeof(float) * 2, 0);
|
|
|
|
gr_render_primitives_2d_immediate(&render_material, PRIM_TYPE_TRISTRIP, &vert_def, 4, glVertices, sizeof(float) * 8);
|
|
}
|
|
|
|
void gr_flash(int r, int g, int b) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
if (!(r || g || b)) {
|
|
return;
|
|
}
|
|
|
|
gr_flash_internal(r, g, b, 255, false);
|
|
}
|
|
|
|
void gr_flash_alpha(int r, int g, int b, int a) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
if (!(r || g || b || a)) {
|
|
return;
|
|
}
|
|
|
|
gr_flash_internal(r, g, b, a, true);
|
|
}
|
|
|
|
static void
|
|
draw_textured_quad(material* mat, float x1, float y1, float u1, float v1, float x2, float y2, float u2, float v2) {
|
|
GR_DEBUG_SCOPE("Draw textured quad");
|
|
|
|
float glVertices[4][4] = {{ x1, y1, u1, v1 },
|
|
{ x1, y2, u1, v2 },
|
|
{ x2, y1, u2, v1 },
|
|
{ x2, y2, u2, v2 }};
|
|
|
|
vertex_layout vert_def;
|
|
|
|
vert_def.add_vertex_component(vertex_format_data::POSITION2, sizeof(float) * 4, 0);
|
|
vert_def.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(float) * 4, sizeof(float) * 2);
|
|
|
|
gr_render_primitives_immediate(mat, PRIM_TYPE_TRISTRIP, &vert_def, 4, glVertices, sizeof(float) * 4 * 4);
|
|
}
|
|
|
|
static void bitmap_ex_internal(int x,
|
|
int y,
|
|
int w,
|
|
int h,
|
|
int sx,
|
|
int sy,
|
|
int resize_mode,
|
|
bool aabitmap,
|
|
bool mirror,
|
|
color* clr,
|
|
float scale_factor = 1.0f) {
|
|
if ((w < 1) || (h < 1)) {
|
|
return;
|
|
}
|
|
|
|
if (aabitmap && !clr->is_alphacolor) {
|
|
return;
|
|
}
|
|
|
|
float u0, u1, v0, v1;
|
|
float x1, x2, y1, y2;
|
|
int bw, bh;
|
|
|
|
bool do_resize;
|
|
if (resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1))) {
|
|
do_resize = true;
|
|
} else {
|
|
do_resize = false;
|
|
}
|
|
|
|
bm_get_info(gr_screen.current_bitmap, &bw, &bh);
|
|
|
|
if (scale_factor != 1.0f) {
|
|
bw = static_cast<int>(bw * scale_factor);
|
|
bh = static_cast<int>(bh * scale_factor);
|
|
}
|
|
|
|
u0 = (i2fl(sx) / i2fl(bw));
|
|
v0 = (i2fl(sy) / i2fl(bh));
|
|
|
|
u1 = (i2fl(sx + w) / i2fl(bw));
|
|
v1 = (i2fl(sy + h) / i2fl(bh));
|
|
|
|
x1 = i2fl(x + ((do_resize) ? gr_screen.offset_x_unscaled : gr_screen.offset_x));
|
|
y1 = i2fl(y + ((do_resize) ? gr_screen.offset_y_unscaled : gr_screen.offset_y));
|
|
x2 = x1 + i2fl(w);
|
|
y2 = y1 + i2fl(h);
|
|
|
|
if (do_resize) {
|
|
gr_resize_screen_posf(&x1, &y1, NULL, NULL, resize_mode);
|
|
gr_resize_screen_posf(&x2, &y2, NULL, NULL, resize_mode);
|
|
}
|
|
|
|
if (mirror) {
|
|
float temp = u0;
|
|
u0 = u1;
|
|
u1 = temp;
|
|
}
|
|
|
|
material render_mat;
|
|
render_mat.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
|
|
render_mat.set_depth_mode(ZBUFFER_TYPE_NONE);
|
|
render_mat.set_texture_map(TM_BASE_TYPE, gr_screen.current_bitmap);
|
|
render_mat.set_color(clr->red, clr->green, clr->blue, clr->alpha);
|
|
render_mat.set_cull_mode(false);
|
|
|
|
if (aabitmap) {
|
|
render_mat.set_texture_type(material::TEX_TYPE_AABITMAP);
|
|
} else {
|
|
if (bm_has_alpha_channel(gr_screen.current_bitmap)) {
|
|
render_mat.set_texture_type(material::TEX_TYPE_XPARENT);
|
|
} else {
|
|
render_mat.set_texture_type(material::TEX_TYPE_NORMAL);
|
|
}
|
|
}
|
|
|
|
draw_textured_quad(&render_mat, x1, y1, u0, v0, x2, y2, u1, v1);
|
|
}
|
|
|
|
void gr_aabitmap(int x, int y, int resize_mode, bool mirror, float scale_factor) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Draw AA-bitmap");
|
|
|
|
int w, h, do_resize;
|
|
|
|
bm_get_info(gr_screen.current_bitmap, &w, &h);
|
|
|
|
if (scale_factor != 1.0f) {
|
|
w = static_cast<int>(w * scale_factor);
|
|
h = static_cast<int>(h * scale_factor);
|
|
}
|
|
|
|
if (resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1))) {
|
|
do_resize = 1;
|
|
} else {
|
|
do_resize = 0;
|
|
}
|
|
|
|
int dx1 = x;
|
|
int dx2 = x + w - 1;
|
|
int dy1 = y;
|
|
int dy2 = y + h - 1;
|
|
int sx = 0, sy = 0;
|
|
|
|
int clip_left = ((do_resize) ? gr_screen.clip_left_unscaled : gr_screen.clip_left);
|
|
int clip_right = ((do_resize) ? gr_screen.clip_right_unscaled : gr_screen.clip_right);
|
|
int clip_top = ((do_resize) ? gr_screen.clip_top_unscaled : gr_screen.clip_top);
|
|
int clip_bottom = ((do_resize) ? gr_screen.clip_bottom_unscaled : gr_screen.clip_bottom);
|
|
|
|
if ((dx1 > clip_right) || (dx2 < clip_left)) {
|
|
return;
|
|
}
|
|
|
|
if ((dy1 > clip_bottom) || (dy2 < clip_top)) {
|
|
return;
|
|
}
|
|
|
|
int delta_left = 0, delta_right = 0;
|
|
|
|
if (dx1 < clip_left) {
|
|
delta_left = clip_left - dx1;
|
|
dx1 = clip_left;
|
|
}
|
|
|
|
if (dy1 < clip_top) {
|
|
sy = clip_top - dy1;
|
|
dy1 = clip_top;
|
|
}
|
|
|
|
if (dx2 > clip_right) {
|
|
delta_right = dx2 - clip_right;
|
|
dx2 = clip_right;
|
|
}
|
|
|
|
if (dy2 > clip_bottom) {
|
|
dy2 = clip_bottom;
|
|
}
|
|
|
|
// When mirrored, the destination's left edge maps to the source's right edge,
|
|
// so a left-side dest clip drops pixels from the right of the source (and vice versa).
|
|
sx = mirror ? delta_right : delta_left;
|
|
|
|
if ((sx < 0) || (sy < 0)) {
|
|
return;
|
|
}
|
|
|
|
if ((sx >= w) || (sy >= h)) {
|
|
return;
|
|
}
|
|
|
|
// Draw bitmap bm[sx,sy] into (dx1,dy1)-(dx2,dy2)
|
|
bitmap_ex_internal(dx1,
|
|
dy1,
|
|
(dx2 - dx1 + 1),
|
|
(dy2 - dy1 + 1),
|
|
sx,
|
|
sy,
|
|
resize_mode,
|
|
true,
|
|
mirror,
|
|
&GR_CURRENT_COLOR,
|
|
scale_factor);
|
|
}
|
|
void gr_aabitmap_ex(int x, int y, int w, int h, int sx, int sy, int resize_mode, bool mirror, float scale_factor) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
int reclip;
|
|
#ifndef NDEBUG
|
|
int count = 0;
|
|
#endif
|
|
|
|
int bw, bh, do_resize;
|
|
|
|
bm_get_info(gr_screen.current_bitmap, &bw, &bh);
|
|
|
|
if (scale_factor != 1.0f) {
|
|
bw = fl2i(bw * scale_factor);
|
|
bh = fl2i(bh * scale_factor);
|
|
|
|
// If we're scaling then we need to scale these to make sure the right and bottom clip planes are scaled as well
|
|
w = fl2i(w * scale_factor);
|
|
h = fl2i(h * scale_factor);
|
|
|
|
sx = fl2i(sx * scale_factor);
|
|
sy = fl2i(sy * scale_factor);
|
|
}
|
|
|
|
int dx1 = x;
|
|
int dx2 = x + w - 1;
|
|
int dy1 = y;
|
|
int dy2 = y + h - 1;
|
|
|
|
if (resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1))) {
|
|
do_resize = 1;
|
|
} else {
|
|
do_resize = 0;
|
|
}
|
|
|
|
int clip_left = ((do_resize) ? gr_screen.clip_left_unscaled : gr_screen.clip_left);
|
|
int clip_right = ((do_resize) ? gr_screen.clip_right_unscaled : gr_screen.clip_right);
|
|
int clip_top = ((do_resize) ? gr_screen.clip_top_unscaled : gr_screen.clip_top);
|
|
int clip_bottom = ((do_resize) ? gr_screen.clip_bottom_unscaled : gr_screen.clip_bottom);
|
|
|
|
do {
|
|
reclip = 0;
|
|
|
|
#ifndef NDEBUG
|
|
if (count > 1) {
|
|
Int3();
|
|
}
|
|
|
|
count++;
|
|
#endif
|
|
|
|
if ((dx1 > clip_right) || (dx2 < clip_left)) {
|
|
return;
|
|
}
|
|
|
|
if ((dy1 > clip_bottom) || (dy2 < clip_top)) {
|
|
return;
|
|
}
|
|
|
|
if (dx1 < clip_left) {
|
|
int delta = clip_left - dx1;
|
|
// When mirrored, dest-left maps to source-right, so drop from the source's right
|
|
// edge (handled via reduced w below) instead of advancing sx.
|
|
if (!mirror) {
|
|
sx += delta;
|
|
}
|
|
dx1 = clip_left;
|
|
}
|
|
|
|
if (dy1 < clip_top) {
|
|
sy += clip_top - dy1;
|
|
dy1 = clip_top;
|
|
}
|
|
|
|
if (dx2 > clip_right) {
|
|
int delta = dx2 - clip_right;
|
|
// When mirrored, dest-right maps to source-left, so advance sx by the right delta.
|
|
if (mirror) {
|
|
sx += delta;
|
|
}
|
|
dx2 = clip_right;
|
|
}
|
|
|
|
if (dy2 > clip_bottom) {
|
|
dy2 = clip_bottom;
|
|
}
|
|
|
|
if (sx < 0) {
|
|
dx1 -= sx;
|
|
sx = 0;
|
|
reclip = 1;
|
|
}
|
|
|
|
if (sy < 0) {
|
|
dy1 -= sy;
|
|
sy = 0;
|
|
reclip = 1;
|
|
}
|
|
|
|
w = dx2 - dx1 + 1;
|
|
h = dy2 - dy1 + 1;
|
|
|
|
if (sx + w > bw) {
|
|
w = bw - sx;
|
|
dx2 = dx1 + w - 1;
|
|
}
|
|
|
|
if (sy + h > bh) {
|
|
h = bh - sy;
|
|
dy2 = dy1 + h - 1;
|
|
}
|
|
|
|
if ((w < 1) || (h < 1)) {
|
|
// clipped away!
|
|
return;
|
|
}
|
|
} while (reclip);
|
|
|
|
// Make sure clipping algorithm works
|
|
#ifndef NDEBUG
|
|
Assert(w > 0);
|
|
Assert(h > 0);
|
|
Assert(w == (dx2 - dx1 + 1));
|
|
Assert(h == (dy2 - dy1 + 1));
|
|
Assert(sx >= 0);
|
|
Assert(sy >= 0);
|
|
Assert(sx + w <= bw);
|
|
Assert(sy + h <= bh);
|
|
Assert(dx2 >= dx1);
|
|
Assert(dy2 >= dy1);
|
|
Assert((dx1 >= clip_left) && (dx1 <= clip_right));
|
|
Assert((dx2 >= clip_left) && (dx2 <= clip_right));
|
|
Assert((dy1 >= clip_top) && (dy1 <= clip_bottom));
|
|
Assert((dy2 >= clip_top) && (dy2 <= clip_bottom));
|
|
#endif
|
|
|
|
// We now have dx1,dy1 and dx2,dy2 and sx, sy all set validly within clip regions.
|
|
bitmap_ex_internal(dx1,
|
|
dy1,
|
|
(dx2 - dx1 + 1),
|
|
(dy2 - dy1 + 1),
|
|
sx,
|
|
sy,
|
|
resize_mode,
|
|
true,
|
|
mirror,
|
|
&GR_CURRENT_COLOR,
|
|
scale_factor);
|
|
}
|
|
//these are penguins bitmap functions
|
|
void gr_bitmap_ex(int x, int y, int w, int h, int sx, int sy, int resize_mode, bool mirror, float scale_factor) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
int reclip;
|
|
#ifndef NDEBUG
|
|
int count = 0;
|
|
#endif
|
|
|
|
int bw, bh, do_resize;
|
|
|
|
bm_get_info(gr_screen.current_bitmap, &bw, &bh);
|
|
|
|
if (scale_factor != 1.0f) {
|
|
bw = fl2i(bw * scale_factor);
|
|
bh = fl2i(bh * scale_factor);
|
|
|
|
// If we're scaling then we need to scale these to make sure the right and bottom clip planes are scaled as well
|
|
w = fl2i(w * scale_factor);
|
|
h = fl2i(h * scale_factor);
|
|
|
|
sx = fl2i(sx * scale_factor);
|
|
sy = fl2i(sy * scale_factor);
|
|
}
|
|
|
|
int dx1 = x;
|
|
int dx2 = x + w - 1;
|
|
int dy1 = y;
|
|
int dy2 = y + h - 1;
|
|
|
|
if (resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1))) {
|
|
do_resize = 1;
|
|
} else {
|
|
do_resize = 0;
|
|
}
|
|
|
|
int clip_left = ((do_resize) ? gr_screen.clip_left_unscaled : gr_screen.clip_left);
|
|
int clip_right = ((do_resize) ? gr_screen.clip_right_unscaled : gr_screen.clip_right);
|
|
int clip_top = ((do_resize) ? gr_screen.clip_top_unscaled : gr_screen.clip_top);
|
|
int clip_bottom = ((do_resize) ? gr_screen.clip_bottom_unscaled : gr_screen.clip_bottom);
|
|
|
|
do {
|
|
reclip = 0;
|
|
|
|
#ifndef NDEBUG
|
|
if (count > 1) {
|
|
Int3();
|
|
}
|
|
|
|
count++;
|
|
#endif
|
|
|
|
if ((dx1 > clip_right) || (dx2 < clip_left)) {
|
|
return;
|
|
}
|
|
|
|
if ((dy1 > clip_bottom) || (dy2 < clip_top)) {
|
|
return;
|
|
}
|
|
|
|
if (dx1 < clip_left) {
|
|
int delta = clip_left - dx1;
|
|
// When mirrored, dest-left maps to source-right, so drop from the source's right
|
|
// edge (handled via reduced w below) instead of advancing sx.
|
|
if (!mirror) {
|
|
sx += delta;
|
|
}
|
|
dx1 = clip_left;
|
|
}
|
|
|
|
if (dy1 < clip_top) {
|
|
sy += clip_top - dy1;
|
|
dy1 = clip_top;
|
|
}
|
|
|
|
if (dx2 > clip_right) {
|
|
int delta = dx2 - clip_right;
|
|
// When mirrored, dest-right maps to source-left, so advance sx by the right delta.
|
|
if (mirror) {
|
|
sx += delta;
|
|
}
|
|
dx2 = clip_right;
|
|
}
|
|
|
|
if (dy2 > clip_bottom) {
|
|
dy2 = clip_bottom;
|
|
}
|
|
|
|
if (sx < 0) {
|
|
dx1 -= sx;
|
|
sx = 0;
|
|
reclip = 1;
|
|
}
|
|
|
|
if (sy < 0) {
|
|
dy1 -= sy;
|
|
sy = 0;
|
|
reclip = 1;
|
|
}
|
|
|
|
w = dx2 - dx1 + 1;
|
|
h = dy2 - dy1 + 1;
|
|
|
|
if ((sx + w) > bw) {
|
|
w = bw - sx;
|
|
dx2 = dx1 + w - 1;
|
|
}
|
|
|
|
if ((sy + h) > bh) {
|
|
h = bh - sy;
|
|
dy2 = dy1 + h - 1;
|
|
}
|
|
|
|
if ((w < 1) || (h < 1)) {
|
|
// clipped away!
|
|
return;
|
|
}
|
|
} while (reclip);
|
|
|
|
// Make sure clipping algorithm works
|
|
#ifndef NDEBUG
|
|
Assert(w > 0);
|
|
Assert(h > 0);
|
|
Assert(w == (dx2 - dx1 + 1));
|
|
Assert(h == (dy2 - dy1 + 1));
|
|
Assert(sx >= 0);
|
|
Assert(sy >= 0);
|
|
Assert((sx + w) <= bw);
|
|
Assert((sy + h) <= bh);
|
|
Assert(dx2 >= dx1);
|
|
Assert(dy2 >= dy1);
|
|
Assert((dx1 >= clip_left) && (dx1 <= clip_right));
|
|
Assert((dx2 >= clip_left) && (dx2 <= clip_right));
|
|
Assert((dy1 >= clip_top) && (dy1 <= clip_bottom));
|
|
Assert((dy2 >= clip_top) && (dy2 <= clip_bottom));
|
|
#endif
|
|
|
|
color clr;
|
|
gr_init_alphacolor(&clr, 255, 255, 255, fl2i(gr_screen.current_alpha * 255.0f));
|
|
|
|
// We now have dx1,dy1 and dx2,dy2 and sx, sy all set validly within clip regions.
|
|
bitmap_ex_internal(dx1, dy1, (dx2 - dx1 + 1), (dy2 - dy1 + 1), sx, sy, resize_mode, false, mirror, &clr, scale_factor);
|
|
}
|
|
|
|
#define MAX_VERTS_PER_DRAW 300
|
|
struct v4 {
|
|
float x, y, u, v;
|
|
};
|
|
static v4 String_render_buff[MAX_VERTS_PER_DRAW];
|
|
|
|
static void gr_string_old(float sx,
|
|
float sy,
|
|
const char* s,
|
|
const char* end,
|
|
const font::font* fontData,
|
|
const font::FSFont* fontMetrics,
|
|
int resize_mode,
|
|
float scaleMultiplier)
|
|
{
|
|
GR_DEBUG_SCOPE("Render VFNT string");
|
|
|
|
const float height = fontMetrics->getHeight();
|
|
const bool canAutoScale = fontMetrics->getAutoScaleBehavior();
|
|
const bool canScale = fontMetrics->getScaleBehavior();
|
|
const float tabWidth = fontMetrics->getTabWidth();
|
|
|
|
float x = sx;
|
|
float y = sy;
|
|
|
|
material render_mat;
|
|
render_mat.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
|
|
render_mat.set_depth_mode(ZBUFFER_TYPE_NONE);
|
|
render_mat.set_texture_map(TM_BASE_TYPE, fontData->bitmap_id);
|
|
render_mat.set_color(GR_CURRENT_COLOR.red,
|
|
GR_CURRENT_COLOR.green,
|
|
GR_CURRENT_COLOR.blue,
|
|
GR_CURRENT_COLOR.alpha);
|
|
render_mat.set_cull_mode(false);
|
|
render_mat.set_texture_type(material::TEX_TYPE_AABITMAP);
|
|
|
|
int buffer_offset = 0;
|
|
|
|
int ibw, ibh;
|
|
|
|
bm_get_info(fontData->bitmap_id, &ibw, &ibh);
|
|
|
|
float bw = i2fl(ibw);
|
|
float bh = i2fl(ibh);
|
|
|
|
bool do_resize;
|
|
if (resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1))) {
|
|
do_resize = true;
|
|
} else {
|
|
do_resize = false;
|
|
}
|
|
|
|
int clip_left = ((do_resize) ? gr_screen.clip_left_unscaled : gr_screen.clip_left);
|
|
int clip_right = ((do_resize) ? gr_screen.clip_right_unscaled : gr_screen.clip_right);
|
|
int clip_top = ((do_resize) ? gr_screen.clip_top_unscaled : gr_screen.clip_top);
|
|
int clip_bottom = ((do_resize) ? gr_screen.clip_bottom_unscaled : gr_screen.clip_bottom);
|
|
|
|
vertex_layout vert_def;
|
|
|
|
vert_def.add_vertex_component(vertex_format_data::POSITION2, sizeof(v4), (int)offsetof(v4, x));
|
|
vert_def.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(v4), (int)offsetof(v4, u));
|
|
|
|
gr_set_2d_matrix();
|
|
|
|
float scale_factor = (canScale && !Fred_running) ? get_font_scale_factor() : 1.0f;
|
|
|
|
if (canAutoScale && !Fred_running) {
|
|
float autoSizedFont = calculate_auto_font_size(height);
|
|
|
|
// Calculate the auto scale factor
|
|
float auto_scale_factor = autoSizedFont / height;
|
|
scale_factor *= auto_scale_factor;
|
|
}
|
|
|
|
scale_factor *= scaleMultiplier;
|
|
|
|
int letter;
|
|
while (s < end) {
|
|
// Handle line breaks
|
|
while (*s == '\n') {
|
|
s++;
|
|
y += height * scale_factor;
|
|
x = sx;
|
|
}
|
|
|
|
if (*s == 0) {
|
|
break;
|
|
}
|
|
|
|
// Handle tabs
|
|
if (*s == '\t') {
|
|
s++;
|
|
x += tabWidth * scale_factor;
|
|
continue;
|
|
}
|
|
|
|
// Get character width and spacing
|
|
int raw_width = 0, raw_spacing = 0;
|
|
letter = font::get_char_width_old(fontData,
|
|
(ubyte)s[0],
|
|
(ubyte)s[1],
|
|
&raw_width,
|
|
&raw_spacing);
|
|
s++;
|
|
|
|
// Not in font, draw as space
|
|
if (letter < 0) {
|
|
x += raw_spacing * scale_factor;
|
|
continue;
|
|
}
|
|
|
|
// UV coordinates
|
|
int u = fontData->bm_u[letter];
|
|
int v = fontData->bm_v[letter];
|
|
float char_width = i2fl(raw_width);
|
|
float char_height = i2fl(height);
|
|
|
|
// Scale output dimensions and positions
|
|
float xc = x; // Keep the X position unscaled
|
|
float yc = y; // Keep the Y position unscaled
|
|
float wc = char_width * scale_factor; // Scale width
|
|
float hc = char_height * scale_factor; // Scale height
|
|
|
|
// Check if the character is completely out of bounds. This uses scaled width and height
|
|
if ((xc > clip_right) || ((xc + wc) < clip_left) || (yc > clip_bottom) || ((yc + hc) < clip_top)) {
|
|
x += raw_spacing * scale_factor;
|
|
continue;
|
|
}
|
|
|
|
// Apply offsets
|
|
float x1 = xc + ((do_resize) ? gr_screen.offset_x_unscaled : gr_screen.offset_x);
|
|
float y1 = yc + ((do_resize) ? gr_screen.offset_y_unscaled : gr_screen.offset_y);
|
|
float x2 = x1 + wc;
|
|
float y2 = y1 + hc;
|
|
|
|
// Resize screen positions
|
|
if (do_resize) {
|
|
gr_resize_screen_posf(&x1, &y1, NULL, NULL, resize_mode);
|
|
gr_resize_screen_posf(&x2, &y2, NULL, NULL, resize_mode);
|
|
}
|
|
|
|
// Get the character from the UV
|
|
float u0 = u / bw;
|
|
float v0 = v / bh;
|
|
float u1 = (u + char_width) / bw;
|
|
float v1 = (v + char_height) / bh;
|
|
|
|
// Add vertices for the character
|
|
String_render_buff[buffer_offset++] = {x1, y1, u0, v0};
|
|
String_render_buff[buffer_offset++] = {x1, y2, u0, v1};
|
|
String_render_buff[buffer_offset++] = {x2, y1, u1, v0};
|
|
String_render_buff[buffer_offset++] = {x1, y2, u0, v1};
|
|
String_render_buff[buffer_offset++] = {x2, y1, u1, v0};
|
|
String_render_buff[buffer_offset++] = {x2, y2, u1, v1};
|
|
|
|
// If the buffer is full, render it now
|
|
if (buffer_offset == MAX_VERTS_PER_DRAW) {
|
|
gr_render_primitives_immediate(&render_mat,
|
|
PRIM_TYPE_TRIS,
|
|
&vert_def,
|
|
buffer_offset,
|
|
String_render_buff,
|
|
sizeof(v4) * buffer_offset);
|
|
buffer_offset = 0;
|
|
}
|
|
|
|
// Advance x for the next character
|
|
x += raw_spacing * scale_factor;
|
|
}
|
|
|
|
// Render remaining vertices in the buffer
|
|
if (buffer_offset) {
|
|
gr_render_primitives_immediate(&render_mat,
|
|
PRIM_TYPE_TRIS,
|
|
&vert_def,
|
|
buffer_offset,
|
|
String_render_buff,
|
|
sizeof(v4) * buffer_offset);
|
|
}
|
|
|
|
gr_end_2d_matrix();
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
bool buffering_nanovg = false; //!< flag for when NanoVG buffering is enabled
|
|
|
|
void setupDrawingState(graphics::paths::PathRenderer* path) {
|
|
path->resetState();
|
|
}
|
|
|
|
void setupTransforms(graphics::paths::PathRenderer* path, int resize_mode) {
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
float w = 1.0f;
|
|
float h = 1.0f;
|
|
bool do_resize = gr_resize_screen_posf(&x, &y, &w, &h, resize_mode);
|
|
|
|
if (gr_screen.rendering_to_texture != -1) {
|
|
// Flip the Y-axis when rendering to texture
|
|
path->translate(0.f, i2fl(gr_screen.max_h));
|
|
path->scale(1.f, -1.f);
|
|
}
|
|
|
|
path->translate(x, y);
|
|
path->scale(w, h);
|
|
|
|
int clip_width = ((do_resize) ? gr_screen.clip_width_unscaled : gr_screen.clip_width);
|
|
int clip_height = ((do_resize) ? gr_screen.clip_height_unscaled : gr_screen.clip_height);
|
|
|
|
int offset_x = ((do_resize) ? gr_screen.offset_x_unscaled : gr_screen.offset_x);
|
|
int offset_y = ((do_resize) ? gr_screen.offset_y_unscaled : gr_screen.offset_y);
|
|
|
|
path->translate(i2fl(offset_x), i2fl(offset_y));
|
|
|
|
path->scissor(0.0f, 0.0f, i2fl(clip_width), i2fl(clip_height));
|
|
}
|
|
|
|
graphics::paths::PathRenderer* beginDrawing(int resize_mode) {
|
|
auto path = graphics::paths::PathRenderer::instance();
|
|
|
|
path->saveState();
|
|
setupDrawingState(path);
|
|
|
|
if (!buffering_nanovg) {
|
|
// If buffering is enabled then this has already been called
|
|
path->beginFrame();
|
|
}
|
|
setupTransforms(path, resize_mode);
|
|
|
|
path->beginPath();
|
|
|
|
path->setStrokeWidth(GR_CURRENT_LINE_WIDTH);
|
|
|
|
return path;
|
|
}
|
|
|
|
void endDrawing(graphics::paths::PathRenderer* path) {
|
|
if (!buffering_nanovg) {
|
|
// If buffering is enabled then this will be called later
|
|
path->endFrame();
|
|
}
|
|
path->restoreState();
|
|
}
|
|
}
|
|
|
|
void gr_string(float sx, float sy, const char* s, int resize_mode, float scaleMultiplier, size_t in_length)
|
|
{
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Render string");
|
|
|
|
using namespace font;
|
|
using namespace graphics::paths;
|
|
namespace fo = font;
|
|
|
|
Assertion(s != NULL, "NULL pointer passed to gr_string!");
|
|
|
|
if (!FontManager::isReady() || (*s == '\0')) {
|
|
return;
|
|
}
|
|
|
|
size_t length;
|
|
if (in_length == std::string::npos) {
|
|
length = strlen(s);
|
|
} else {
|
|
length = in_length;
|
|
}
|
|
|
|
FSFont* currentFont = FontManager::getCurrentFont();
|
|
|
|
if (currentFont->getType() == VFNT_FONT) {
|
|
VFNTFont* fnt = static_cast<VFNTFont*>(currentFont);
|
|
fo::font* fontData = fnt->getFontData();
|
|
|
|
gr_string_old(sx, sy, s, s + length, fontData, currentFont, resize_mode, scaleMultiplier);
|
|
} else if (currentFont->getType() == NVG_FONT) {
|
|
GR_DEBUG_SCOPE("Render TTF string");
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
|
|
auto nvgFont = static_cast<NVGFont*>(currentFont);
|
|
|
|
float scale_factor = (nvgFont->getScaleBehavior() && !Fred_running) ? get_font_scale_factor() : 1.0f;
|
|
scale_factor *= scaleMultiplier;
|
|
|
|
float originalSize = nvgFont->getSize();
|
|
float scaledSize = originalSize * scale_factor;
|
|
|
|
// Calculate the offset to center the text
|
|
float offsetX = 0.0f;
|
|
// This is a compromise to try and size the text around center to minimize text offsets during scaling behavior.
|
|
// TODO Update this if multiline text is found to be negatively affected or a proper method of setting text anchors is added
|
|
float offsetY = (scaledSize - originalSize) * 0.5f;
|
|
|
|
path->translate(sx - offsetX, sy - offsetY);
|
|
|
|
path->fontFaceId(nvgFont->getHandle());
|
|
path->fontSize(scaledSize);
|
|
path->textLetterSpacing(nvgFont->getLetterSpacing());
|
|
path->textAlign(static_cast<TextAlign>(ALIGN_TOP | ALIGN_LEFT));
|
|
|
|
float scaleX = 1.0f;
|
|
float scaleY = 1.0f;
|
|
gr_resize_screen_posf(nullptr, nullptr, &scaleX, &scaleY, resize_mode);
|
|
|
|
float invscaleX = 1.f / scaleX;
|
|
|
|
bool twoPassRequired = false;
|
|
|
|
path->setFillColor(&GR_CURRENT_COLOR);
|
|
|
|
// Do a two pass algorithm, first render text using NanoVG, then render old characters
|
|
for (int pass = 0; pass < 2; ++pass) {
|
|
const char* text = s;
|
|
size_t textLen = length;
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
|
|
size_t tokenLength;
|
|
while ((tokenLength = NVGFont::getTokenLength(text, textLen)) > 0) {
|
|
textLen -= tokenLength;
|
|
|
|
bool doRender = true;
|
|
bool specialChar = false;
|
|
if (tokenLength == 1) {
|
|
// We may have encountered a special character
|
|
switch (*text) {
|
|
case '\n':
|
|
doRender = false;
|
|
|
|
y += nvgFont->getHeight() * scale_factor;
|
|
x = 0;
|
|
break;
|
|
case '\t':
|
|
doRender = false;
|
|
|
|
x += nvgFont->getTabWidth() * scale_factor;
|
|
break;
|
|
case '\r':
|
|
// Ignore Carriage return chars
|
|
doRender = false;
|
|
break;
|
|
default:
|
|
// Only do special character handling if we are not in unicode mode. Otherwise this is just a normal character for us
|
|
if (!Unicode_text_mode) {
|
|
if (*text >= Lcl_special_chars || *text < 0) {
|
|
specialChar = true;
|
|
twoPassRequired = true;
|
|
}
|
|
else {
|
|
doRender = true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (specialChar) {
|
|
if (pass == 1) {
|
|
// We compute the top offset of the special character by aligning it to the base line of the string
|
|
// This is done by moving to the base line of the string by adding the ascender value and then
|
|
// accounting for the height of the text with the height of the special font
|
|
auto yOffset = nvgFont->getTopOffset() +
|
|
(nvgFont->getAscender() - nvgFont->getSpecialCharacterFont()->h);
|
|
|
|
gr_string_old(sx + x * scaleX,
|
|
sy + (y + yOffset) * scaleY,
|
|
text,
|
|
text + 1,
|
|
nvgFont->getSpecialCharacterFont(),
|
|
nvgFont,
|
|
resize_mode,
|
|
scaleMultiplier);
|
|
}
|
|
|
|
int width;
|
|
int spacing;
|
|
get_char_width_old(nvgFont->getSpecialCharacterFont(), (ubyte)*text, (ubyte)'\0', &width, &spacing);
|
|
|
|
x += i2fl(spacing) * invscaleX;
|
|
} else if (doRender) {
|
|
if (doRender && tokenLength > 0) {
|
|
float advance;
|
|
float currentX = x * scaleX;
|
|
float currentY = y + nvgFont->getTopOffset();
|
|
|
|
if (pass == 0) {
|
|
path->text(currentX, currentY, text, text + tokenLength);
|
|
}
|
|
|
|
advance = path->textBounds(0.f, 0.f, text, text + tokenLength, nullptr);
|
|
x += advance * invscaleX;
|
|
}
|
|
}
|
|
|
|
text = text + tokenLength;
|
|
}
|
|
|
|
if (pass == 0) {
|
|
endDrawing(path);
|
|
}
|
|
|
|
if (!twoPassRequired) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
Error(LOCATION, "Invalid type enumeration for font \"%s\". Get a coder!", currentFont->getName().c_str());
|
|
}
|
|
}
|
|
|
|
void gr_string_outlined(int x, int y, const char* text, const color* foreground, const color* outline, int offset, int resize_mode, float scaleMultiplier, size_t length)
|
|
{
|
|
// draw outline by rendering text at all surrounding offsets
|
|
gr_set_color_fast(outline);
|
|
for (int dx = -offset; dx <= offset; dx++)
|
|
for (int dy = -offset; dy <= offset; dy++)
|
|
if (dx || dy)
|
|
gr_string(x + dx, y + dy, text, resize_mode, scaleMultiplier, length);
|
|
|
|
// draw foreground text on top
|
|
gr_set_color_fast(foreground);
|
|
gr_string(x, y, text, resize_mode, scaleMultiplier, length);
|
|
}
|
|
|
|
static void gr_line(float x1, float y1, float x2, float y2, int resize_mode) {
|
|
auto path = beginDrawing(resize_mode);
|
|
|
|
if ((x1 == x2) && (y1 == y2)) {
|
|
path->circle(x1, y1, 1.5);
|
|
|
|
path->setFillColor(&GR_CURRENT_COLOR);
|
|
path->fill();
|
|
} else {
|
|
path->moveTo(x1, y1);
|
|
path->lineTo(x2, y2);
|
|
|
|
path->setStrokeColor(&GR_CURRENT_COLOR);
|
|
path->stroke();
|
|
}
|
|
|
|
endDrawing(path);
|
|
}
|
|
|
|
void gr_line(int x1, int y1, int x2, int y2, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
gr_line(i2fl(x1), i2fl(y1), i2fl(x2), i2fl(y2), resize_mode);
|
|
}
|
|
|
|
void gr_aaline(vertex* v1, vertex* v2) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
float x1 = v1->screen.xyw.x;
|
|
float y1 = v1->screen.xyw.y;
|
|
float x2 = v2->screen.xyw.x;
|
|
float y2 = v2->screen.xyw.y;
|
|
|
|
// AA is now standard
|
|
gr_line(x1, y1, x2, y2, GR_RESIZE_NONE);
|
|
}
|
|
|
|
void gr_gradient(int x1, int y1, int x2, int y2, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
if (!GR_CURRENT_COLOR.is_alphacolor) {
|
|
gr_line(x1, y1, x2, y2, resize_mode);
|
|
return;
|
|
}
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
|
|
color endColor = GR_CURRENT_COLOR;
|
|
endColor.alpha = 0;
|
|
|
|
auto gradientPaint =
|
|
path->createLinearGradient(i2fl(x1), i2fl(y1), i2fl(x2), i2fl(y2), &GR_CURRENT_COLOR, &endColor);
|
|
|
|
path->moveTo(i2fl(x1), i2fl(y1));
|
|
path->lineTo(i2fl(x2), i2fl(y2));
|
|
|
|
path->setStrokePaint(gradientPaint);
|
|
path->stroke();
|
|
|
|
endDrawing(path);
|
|
}
|
|
void gr_pixel(int x, int y, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
gr_line(x, y, x, y, resize_mode);
|
|
}
|
|
|
|
void gr_circle(int xc, int yc, int d, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
|
|
path->circle(i2fl(xc), i2fl(yc), d / 2.0f);
|
|
path->setFillColor(&GR_CURRENT_COLOR);
|
|
path->fill();
|
|
|
|
endDrawing(path);
|
|
}
|
|
void gr_unfilled_circle(int xc, int yc, int d, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
|
|
path->circle(i2fl(xc), i2fl(yc), d / 2.0f);
|
|
path->setStrokeColor(&GR_CURRENT_COLOR);
|
|
path->stroke();
|
|
|
|
endDrawing(path);
|
|
}
|
|
void gr_arc(int xc, int yc, float r, float angle_start, float angle_end, bool fill, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
// Ensure that angle_start < angle_end
|
|
if (angle_end < angle_start) {
|
|
float temp = angle_start;
|
|
angle_start = angle_end;
|
|
angle_end = temp;
|
|
}
|
|
|
|
using namespace graphics::paths;
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
|
|
if (fill) {
|
|
path->arc(i2fl(xc), i2fl(yc), r, fl_radians(angle_start), fl_radians(angle_end), DIR_CW);
|
|
path->lineTo(i2fl(xc), i2fl(yc));
|
|
|
|
path->setFillColor(&GR_CURRENT_COLOR);
|
|
path->fill();
|
|
} else {
|
|
path->arc(i2fl(xc), i2fl(yc), r, fl_radians(angle_start), fl_radians(angle_end), DIR_CW);
|
|
path->setStrokeColor(&GR_CURRENT_COLOR);
|
|
path->stroke();
|
|
}
|
|
|
|
endDrawing(path);
|
|
}
|
|
void gr_curve(int xc, int yc, int r, int direction, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
using namespace graphics::paths;
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
float centerX, centerY;
|
|
float beginAngle, endAngle;
|
|
|
|
switch (direction) {
|
|
case 0: {
|
|
centerX = i2fl(xc + r);
|
|
centerY = i2fl(yc + r);
|
|
beginAngle = fl_radians(180.f);
|
|
endAngle = fl_radians(270.f);
|
|
break;
|
|
}
|
|
case 1: {
|
|
centerX = i2fl(xc);
|
|
centerY = i2fl(yc + r);
|
|
beginAngle = fl_radians(270.f);
|
|
endAngle = fl_radians(360.f);
|
|
break;
|
|
}
|
|
case 2: {
|
|
centerX = i2fl(xc + r);
|
|
centerY = i2fl(yc);
|
|
beginAngle = fl_radians(90.f);
|
|
endAngle = fl_radians(180.f);
|
|
break;
|
|
}
|
|
case 3: {
|
|
centerX = i2fl(xc);
|
|
centerY = i2fl(yc);
|
|
beginAngle = fl_radians(0.f);
|
|
endAngle = fl_radians(90.f);
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
path->arc(centerX, centerY, i2fl(r), beginAngle, endAngle, DIR_CW);
|
|
path->setStrokeColor(&GR_CURRENT_COLOR);
|
|
path->stroke();
|
|
|
|
endDrawing(path);
|
|
}
|
|
|
|
void gr_rect(int x, int y, int w, int h, int resize_mode, float angle) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
if (angle != 0) {
|
|
// If we don't do this translation before and after rotating, the rotation will use 0,0 as the pivot, flinging the rectangle far away.
|
|
float offsetX = x + w / 2.0f;
|
|
float offsetY = y + h / 2.0f;
|
|
path->translate(offsetX, offsetY);
|
|
path->rotate(angle);
|
|
path->translate(-offsetX, -offsetY);
|
|
}
|
|
path->rectangle(i2fl(x), i2fl(y), i2fl(w), i2fl(h));
|
|
path->setFillColor(&GR_CURRENT_COLOR);
|
|
path->fill();
|
|
|
|
endDrawing(path);
|
|
}
|
|
|
|
void gr_shade(int x, int y, int w, int h, int resize_mode) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
auto r = (int) gr_screen.current_shader.r;
|
|
auto g = (int) gr_screen.current_shader.g;
|
|
auto b = (int) gr_screen.current_shader.b;
|
|
auto a = (int) gr_screen.current_shader.c;
|
|
|
|
color clr;
|
|
gr_init_alphacolor(&clr, r, g, b, a);
|
|
|
|
auto path = beginDrawing(resize_mode);
|
|
|
|
path->rectangle(i2fl(x), i2fl(y), i2fl(w), i2fl(h));
|
|
path->setFillColor(&clr);
|
|
path->fill();
|
|
|
|
endDrawing(path);
|
|
}
|
|
|
|
void gr_2d_start_buffer() {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
Assertion(!buffering_nanovg, "Tried to enable 2D buffering but it was already enabled!");
|
|
|
|
buffering_nanovg = true;
|
|
auto path = graphics::paths::PathRenderer::instance();
|
|
|
|
path->beginFrame();
|
|
}
|
|
|
|
void gr_2d_stop_buffer() {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
Assertion(buffering_nanovg, "Tried to stop 2D buffering but it was not enabled!");
|
|
|
|
buffering_nanovg = false;
|
|
auto path = graphics::paths::PathRenderer::instance();
|
|
|
|
path->endFrame();
|
|
}
|
|
|
|
gr_buffer_handle gr_immediate_buffer_handle;
|
|
static size_t immediate_buffer_offset = 0;
|
|
static size_t immediate_buffer_size = 0;
|
|
static const size_t IMMEDIATE_BUFFER_RESIZE_BLOCK_SIZE = 2048;
|
|
|
|
size_t gr_add_to_immediate_buffer(size_t size, void* data) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return 0;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Add data to immediate buffer");
|
|
|
|
if (!gr_immediate_buffer_handle.isValid()) {
|
|
gr_immediate_buffer_handle = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Dynamic);
|
|
}
|
|
|
|
Assert(size > 0 && data != NULL);
|
|
|
|
if ( immediate_buffer_offset + size > immediate_buffer_size ) {
|
|
// incoming data won't fit the immediate buffer. time to reallocate.
|
|
immediate_buffer_offset = 0;
|
|
immediate_buffer_size += MAX(IMMEDIATE_BUFFER_RESIZE_BLOCK_SIZE, size);
|
|
|
|
gr_update_buffer_data(gr_immediate_buffer_handle, immediate_buffer_size, NULL);
|
|
}
|
|
|
|
// only update a section of the immediate vertex buffer
|
|
gr_update_buffer_data_offset(gr_immediate_buffer_handle, immediate_buffer_offset, size, data);
|
|
|
|
auto old_offset = immediate_buffer_offset;
|
|
|
|
immediate_buffer_offset += size;
|
|
|
|
return old_offset;
|
|
}
|
|
void gr_reset_immediate_buffer() {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
if (!gr_immediate_buffer_handle.isValid()) {
|
|
// we haven't used the immediate buffer yet
|
|
return;
|
|
}
|
|
|
|
// orphan the immediate buffer so we can start fresh in a new frame
|
|
gr_update_buffer_data(gr_immediate_buffer_handle, immediate_buffer_size, NULL);
|
|
|
|
// bring our offset to the beginning of the immediate buffer
|
|
immediate_buffer_offset = 0;
|
|
}
|
|
|
|
void gr_render_primitives_immediate(material* material_info,
|
|
primitive_type prim_type,
|
|
vertex_layout* layout,
|
|
int n_verts,
|
|
void* data,
|
|
size_t size) {
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
auto offset = gr_add_to_immediate_buffer(size, data);
|
|
|
|
gr_render_primitives(material_info, prim_type, layout, 0, n_verts, gr_immediate_buffer_handle, offset);
|
|
}
|
|
|
|
void gr_render_primitives_2d_immediate(material* material_info,
|
|
primitive_type prim_type,
|
|
vertex_layout* layout,
|
|
int n_verts,
|
|
void* data,
|
|
size_t size)
|
|
{
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
gr_set_2d_matrix();
|
|
|
|
gr_render_primitives_immediate(material_info, prim_type, layout, n_verts, data, size);
|
|
|
|
gr_end_2d_matrix();
|
|
}
|
|
|
|
// _->NEW<-_ NEW new bitmap functions -Bobboau
|
|
// takes a list of rectangles that have assosiated rectangles in a texture
|
|
static void draw_bitmap_list(bitmap_rect_list* list, int n_bm, int resize_mode, material* render_mat, float angle = 0.f)
|
|
{
|
|
GR_DEBUG_SCOPE("2D Bitmap list");
|
|
|
|
// adapted from g3_draw_2d_poly_bitmap_list
|
|
|
|
for (int i = 0; i < n_bm; i++) {
|
|
bitmap_2d_list* l = &list[i].screen_rect;
|
|
|
|
// if no valid hight or width values were given get some from the bitmap
|
|
if ((l->w <= 0) || (l->h <= 0)) {
|
|
bm_get_info(gr_screen.current_bitmap, &l->w, &l->h, nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
if (resize_mode != GR_RESIZE_NONE && (gr_screen.custom_size || (gr_screen.rendering_to_texture != -1))) {
|
|
gr_resize_screen_pos(&l->x, &l->y, &l->w, &l->h, resize_mode);
|
|
}
|
|
}
|
|
|
|
auto vert_list = new vertex[6 * n_bm];
|
|
float sw = 0.1f;
|
|
|
|
|
|
for (int i = 0; i < n_bm; i++) {
|
|
// stuff coords
|
|
|
|
bitmap_2d_list* b = &list[i].screen_rect;
|
|
texture_rect_list* t = &list[i].texture_rect;
|
|
|
|
float centerX = 0;
|
|
float centerY = 0;
|
|
|
|
if (angle != 0.f) {
|
|
centerX = (b->x + (b->x + b->w)) / 2.0f;
|
|
centerY = (b->y + (b->y + b->h)) / 2.0f;
|
|
}
|
|
|
|
// tri one
|
|
vertex* V = &vert_list[i * 6];
|
|
|
|
if (angle != 0.f) {
|
|
V->screen.xyw.x = cosf(angle) * (b->x - centerX) - sinf(angle) * (b->y - centerY) + centerX;
|
|
V->screen.xyw.y = sinf(angle) * (b->x - centerX) + cosf(angle) * (b->y - centerY) + centerY;
|
|
}
|
|
else {
|
|
V->screen.xyw.x = (float)b->x;
|
|
V->screen.xyw.y = (float)b->y;
|
|
}
|
|
|
|
V->screen.xyw.w = sw;
|
|
V->texture_position.u = (float)t->u0;
|
|
V->texture_position.v = (float)t->v0;
|
|
V->flags = PF_PROJECTED;
|
|
V->codes = 0;
|
|
|
|
V++;
|
|
if (angle != 0.f) {
|
|
V->screen.xyw.x = cosf(angle) * (b->x + b->w - centerX) - sinf(angle) * (b->y - centerY) + centerX;
|
|
V->screen.xyw.y = sinf(angle) * (b->x + b->w - centerX) + cosf(angle) * (b->y - centerY) + centerY;
|
|
}
|
|
else {
|
|
V->screen.xyw.x = (float)(b->x + b->w);
|
|
V->screen.xyw.y = (float)b->y;
|
|
}
|
|
|
|
V->screen.xyw.w = sw;
|
|
V->texture_position.u = (float)t->u1;
|
|
V->texture_position.v = (float)t->v0;
|
|
V->flags = PF_PROJECTED;
|
|
V->codes = 0;
|
|
|
|
V++;
|
|
if (angle != 0.f) {
|
|
V->screen.xyw.x = cosf(angle) * (b->x + b->w - centerX) - sinf(angle) * (b->y + b->h - centerY) + centerX;
|
|
V->screen.xyw.y = sinf(angle) * (b->x + b->w - centerX) + cosf(angle) * (b->y + b->h - centerY) + centerY;
|
|
}
|
|
else {
|
|
V->screen.xyw.x = (float)(b->x + b->w);
|
|
V->screen.xyw.y = (float)(b->y + b->h);
|
|
}
|
|
|
|
V->screen.xyw.w = sw;
|
|
V->texture_position.u = (float)t->u1;
|
|
V->texture_position.v = (float)t->v1;
|
|
V->flags = PF_PROJECTED;
|
|
V->codes = 0;
|
|
|
|
// tri two
|
|
V++;
|
|
if (angle != 0.f) {
|
|
V->screen.xyw.x = cosf(angle) * (b->x - centerX) - sinf(angle) * (b->y - centerY) + centerX;
|
|
V->screen.xyw.y = sinf(angle) * (b->x - centerX) + cosf(angle) * (b->y - centerY) + centerY;
|
|
}
|
|
else {
|
|
V->screen.xyw.x = (float)b->x;
|
|
V->screen.xyw.y = (float)b->y;
|
|
}
|
|
|
|
V->screen.xyw.w = sw;
|
|
V->texture_position.u = (float)t->u0;
|
|
V->texture_position.v = (float)t->v0;
|
|
V->flags = PF_PROJECTED;
|
|
V->codes = 0;
|
|
|
|
V++;
|
|
if (angle != 0.f) {
|
|
V->screen.xyw.x = cosf(angle) * (b->x + b->w - centerX) - sinf(angle) * (b->y + b->h - centerY) + centerX;
|
|
V->screen.xyw.y = sinf(angle) * (b->x + b->w - centerX) + cosf(angle) * (b->y + b->h - centerY) + centerY;
|
|
}
|
|
else {
|
|
V->screen.xyw.x = (float)(b->x + b->w);
|
|
V->screen.xyw.y = (float)(b->y + b->h);
|
|
}
|
|
|
|
V->screen.xyw.w = sw;
|
|
V->texture_position.u = (float)t->u1;
|
|
V->texture_position.v = (float)t->v1;
|
|
V->flags = PF_PROJECTED;
|
|
V->codes = 0;
|
|
|
|
V++;
|
|
if (angle != 0.f) {
|
|
V->screen.xyw.x = cosf(angle) * (b->x - centerX) - sinf(angle) * (b->y + b->h - centerY) + centerX;
|
|
V->screen.xyw.y = sinf(angle) * (b->x - centerX) + cosf(angle) * (b->y + b->h - centerY) + centerY;
|
|
}
|
|
else {
|
|
V->screen.xyw.x = (float)b->x;
|
|
V->screen.xyw.y = (float)(b->y + b->h);
|
|
}
|
|
|
|
V->screen.xyw.w = sw;
|
|
V->texture_position.u = (float)t->u0;
|
|
V->texture_position.v = (float)t->v1;
|
|
V->flags = PF_PROJECTED;
|
|
V->codes = 0;
|
|
}
|
|
|
|
g3_render_primitives_textured(render_mat, vert_list, 6 * n_bm, PRIM_TYPE_TRIS, true);
|
|
|
|
delete[] vert_list;
|
|
}
|
|
|
|
void gr_bitmap_list(bitmap_rect_list* list, int n_bm, int resize_mode, float angle)
|
|
{
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
material mat_params;
|
|
material_set_interface(&mat_params,
|
|
gr_screen.current_bitmap,
|
|
gr_screen.current_alphablend_mode == GR_ALPHABLEND_FILTER,
|
|
gr_screen.current_alpha);
|
|
|
|
draw_bitmap_list(list, n_bm, resize_mode, &mat_params, angle);
|
|
}
|
|
|
|
void gr_aabitmap_list(bitmap_rect_list* list, int n_bm, int resize_mode, float angle)
|
|
{
|
|
if (gr_screen.mode == GraphicsAPI::Stub) {
|
|
return;
|
|
}
|
|
|
|
material render_mat;
|
|
render_mat.set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
|
|
render_mat.set_depth_mode(ZBUFFER_TYPE_NONE);
|
|
render_mat.set_texture_map(TM_BASE_TYPE, gr_screen.current_bitmap);
|
|
render_mat.set_color(GR_CURRENT_COLOR);
|
|
render_mat.set_cull_mode(false);
|
|
render_mat.set_texture_type(material::TEX_TYPE_AABITMAP);
|
|
|
|
draw_bitmap_list(list, n_bm, resize_mode, &render_mat, angle);
|
|
}
|