#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(bw * scale_factor); bh = static_cast(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(w * scale_factor); h = static_cast(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(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(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(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); }