Files
fs2open.github.com/code/scpui/RocketRenderingInterface.cpp
Taylor Richards 039880491a port to SDL3 (#7220)
Basic port from SDL2 to SDL3. Also updates imgui to 1.92.5.
2026-07-23 15:52:27 +02:00

353 lines
11 KiB
C++

//
//
// Undefine stupid windows API defines that get included by the prefix headers
#ifdef GetNextSibling
#undef GetNextSibling
#endif
#ifdef GetFirstChild
#undef GetFirstChild
#endif
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include "RocketRenderingInterface.h"
#include "RocketFileInterface.h"
#pragma pop_macro("Assert")
#include "globalincs/pstypes.h"
#include "graphics/2d.h"
#include "graphics/generic.h"
#include "graphics/material.h"
#include "mod_table/mod_table.h"
#include "tracing/categories.h"
#include "tracing/tracing.h"
#define BMPMAN_INTERNAL
#include "bmpman/bm_internal.h"
using namespace Rocket::Core;
namespace scpui {
RocketRenderingInterface::RocketRenderingInterface()
{
renderOffset.x = 0.0f;
renderOffset.y = 0.0f;
vertex_stream_buffer = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Streaming);
index_stream_buffer = gr_create_buffer(BufferType::Index, BufferUsageHint::Streaming);
layout.add_vertex_component(vertex_format_data::POSITION2, sizeof(Vertex), offsetof(Vertex, position));
layout.add_vertex_component(vertex_format_data::COLOR4, sizeof(Vertex), offsetof(Vertex, colour));
layout.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(Vertex), offsetof(Vertex, tex_coord));
}
RocketRenderingInterface::~RocketRenderingInterface()
{
if (vertex_stream_buffer.isValid()) {
gr_delete_buffer(vertex_stream_buffer);
}
if (index_stream_buffer.isValid()) {
gr_delete_buffer(index_stream_buffer);
}
}
RocketRenderingInterface::Texture* RocketRenderingInterface::get_texture(Rocket::Core::TextureHandle handle)
{
return reinterpret_cast<Texture*>(handle);
}
Rocket::Core::TextureHandle RocketRenderingInterface::get_texture_handle(RocketRenderingInterface::Texture* bitmap)
{
return reinterpret_cast<Rocket::Core::TextureHandle>(bitmap);
}
CompiledGeometryHandle RocketRenderingInterface::CompileGeometry(Vertex* vertices, int num_vertices, int* indices,
int num_indices, TextureHandle texture)
{
TRACE_SCOPE(tracing::RocketCompileGeometry);
GR_DEBUG_SCOPE("libRocket::CompileGeometry");
auto* geom = new CompiledGeometry();
geom->vertex_buffer = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Static);
gr_update_buffer_data(geom->vertex_buffer, num_vertices * sizeof(Vertex), reinterpret_cast<void*>(vertices));
geom->index_buffer = gr_create_buffer(BufferType::Index, BufferUsageHint::Static);
gr_update_buffer_data(geom->index_buffer, num_indices * sizeof(int), reinterpret_cast<void*>(indices));
geom->num_elements = num_indices;
geom->texture = get_texture(texture);
return reinterpret_cast<CompiledGeometryHandle>(geom);
}
void RocketRenderingInterface::RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation)
{
TRACE_SCOPE(tracing::RocketRenderCompiledGeometry);
GR_DEBUG_SCOPE("libRocket::RenderCompiledGeometry");
auto geom = reinterpret_cast<CompiledGeometry*>(geometry);
auto bitmap = -1;
if (geom->texture != nullptr) {
if (geom->texture->is_animation) {
bitmap = geom->texture->animation.bitmap_id;
} else {
bitmap = geom->texture->bm_handle;
}
}
renderGeometry(geom->vertex_buffer, geom->index_buffer, geom->num_elements, bitmap, translation);
}
void RocketRenderingInterface::ReleaseCompiledGeometry(CompiledGeometryHandle geometry)
{
GR_DEBUG_SCOPE("libRocket::ReleaseCompiledGeometry");
auto geom = reinterpret_cast<CompiledGeometry*>(geometry);
gr_delete_buffer(geom->vertex_buffer);
gr_delete_buffer(geom->index_buffer);
delete geom;
}
void RocketRenderingInterface::EnableScissorRegion(bool enable)
{
GR_DEBUG_SCOPE("libRocket::EnableScissorRegion");
if (!enable) {
// Disable clipping if requested
gr_reset_clip();
}
// If clipping is active then SetScissorRegion will be called next
clipping_active = enable;
}
void RocketRenderingInterface::SetScissorRegion(int x, int y, int width, int height)
{
GR_DEBUG_SCOPE("libRocket::SetScissorRegion");
if (clipping_active) {
gr_set_clip((int)(x + renderOffset.x), (int)(y + renderOffset.y), width, height, GR_RESIZE_NONE);
}
}
bool RocketRenderingInterface::LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions,
const String& source)
{
TRACE_SCOPE(tracing::RocketLoadTexture);
GR_DEBUG_SCOPE("libRocket::LoadTexture");
if (source.Find("data:image/") == 0) {
//Special mode to catch blob textures
String submode = source.Substring(11);
if (submode.Find("png;base64,") == 0) {
SCP_string data = submode.Substring(11).CString();
int w, h, bpp;
png_read_header(data, &w, &h, &bpp);
if (w * h < 0)
return false;
//if it's not 32-bit, we expand when we read it
bpp = 32;
int d_size = bpp >> 3;
//we waste memory if it turns out to be 24-bit, but the way this whole thing works is dodgy anyway
ubyte* rawdata = new ubyte[w * h * d_size];
if (rawdata == nullptr)
return false;
memset(rawdata, 0, w * h * d_size);
png_read_bitmap(data, rawdata, &bpp);
texture_dimensions = { w, h };
bool success = RocketRenderingInterface::GenerateTexture(texture_handle, rawdata, texture_dimensions);
delete[] rawdata;
return success;
}
else if (submode.Find("bmpman,") == 0) {
int handle = std::atoi(submode.Substring(7).CString());
auto* entry = bm_get_entry(handle);
if (entry->handle != handle)
return false;
entry->load_count++;
bm_get_info(handle, &texture_dimensions.x, &texture_dimensions.y);
std::unique_ptr<Texture> tex(new Texture());
tex->bm_handle = handle;
texture_handle = get_texture_handle(tex.release());
return true;
}
else {
return false;
}
}
SCP_string filename;
int dir_type;
if (!RocketFileInterface::getCFilePath(source, filename, dir_type)) {
return false;
}
auto period_pos = filename.rfind('.');
if (period_pos != SCP_string::npos) {
filename = filename.substr(0, period_pos);
}
std::unique_ptr<Texture> tex(new Texture());
// If there is a file that ends with an animation extension, try to load that
if (generic_anim_init_and_stream(&tex->animation, filename.c_str(), BM_TYPE_NONE, SCPUI_loads_hi_res_animations) ==
0) {
tex->is_animation = true;
texture_dimensions.x = tex->animation.width;
texture_dimensions.y = tex->animation.height;
}
if (!tex->is_animation) {
// Try to load as standalone image instead
auto id = bm_load_either(filename.c_str(), nullptr, nullptr, nullptr, false, dir_type);
if (id < 0) {
return false;
}
int w, h;
bm_get_info(id, &w, &h);
texture_dimensions.x = w;
texture_dimensions.y = h;
tex->bm_handle = id;
}
// We give the pointer to libRocket now so we release it from our unique ptr
texture_handle = get_texture_handle(tex.release());
return true;
}
bool RocketRenderingInterface::GenerateTexture(TextureHandle& texture_handle, const Rocket::Core::byte* source,
const Vector2i& source_dimensions)
{
TRACE_SCOPE(tracing::RocketGenerateTexture);
GR_DEBUG_SCOPE("libRocket::GenerateTexture");
auto size = (size_t)(source_dimensions.x * source_dimensions.y * 4); // RGBA format
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
memcpy(buffer.get(), source, size);
auto id = bm_create(32, source_dimensions.x, source_dimensions.y, buffer.get());
if (id < 0) {
return false;
}
auto* tex = new Texture();
tex->bm_handle = id;
tex->data = std::move(buffer);
texture_handle = get_texture_handle(tex);
return true;
}
void RocketRenderingInterface::ReleaseTexture(TextureHandle texture)
{
GR_DEBUG_SCOPE("libRocket::ReleaseTexture");
Assertion(texture, "Invalid texture handle!");
auto tex = get_texture(texture);
if (tex->is_animation) {
generic_anim_unload(&tex->animation);
} else {
bm_release(tex->bm_handle);
}
delete tex;
}
void RocketRenderingInterface::RenderGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices,
TextureHandle texture, const Vector2f& translation)
{
TRACE_SCOPE(tracing::RocketRenderGeometry);
GR_DEBUG_SCOPE("libRocket::RenderGeometry");
gr_update_buffer_data(vertex_stream_buffer, sizeof(*vertices) * num_vertices, vertices);
gr_update_buffer_data(index_stream_buffer, sizeof(*indices) * num_indices, indices);
int bitmap;
if (texture == 0) {
bitmap = -1;
} else {
if (get_texture(texture)->is_animation) {
bitmap = get_texture(texture)->animation.bitmap_id;
} else {
bitmap = get_texture(texture)->bm_handle;
}
}
renderGeometry(vertex_stream_buffer, index_stream_buffer, num_indices, bitmap, translation);
}
void RocketRenderingInterface::setRenderOffset(const vec2d& render_offset) { renderOffset = render_offset; }
float RocketRenderingInterface::GetPixelsPerInch()
{
// TODO: SDL3 => The 3.6 API adds SDL_IsPhone(), which would be a more accurate
// check for this. So look into replacing these defines when we
// eventually move to 3.6+.
#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_ANDROID)
const bool is_mobile = true;
#else
const bool is_mobile = false;
#endif
// This isn't reliable. Docs say to multiply by 160 for mobile
// and 96 for everything else.
return SDL_GetWindowDisplayScale(os::getSDLMainWindow()) * (is_mobile ? 160.0f : 96.0f);
}
void RocketRenderingInterface::renderGeometry(gr_buffer_handle vertex_buffer,
gr_buffer_handle index_buffer,
int num_elements,
int bitmap,
const Rocket::Core::Vector2f& translation)
{
interface_material material;
vec2d transl;
transl.x = translation.x + renderOffset.x;
transl.y = translation.y + renderOffset.y;
material_set_rocket_interface(&material, bitmap, transl, horizontal_swipe_offset);
gr_render_rocket_primitives(&material, PRIM_TYPE_TRIS, &layout, num_elements, vertex_buffer, index_buffer);
}
int RocketRenderingInterface::getBitmapNum(Rocket::Core::TextureHandle handle)
{
int bitmap;
if (handle == 0) {
bitmap = -1;
} else {
bitmap = get_texture(handle)->bm_handle;
}
return bitmap;
}
void RocketRenderingInterface::advanceAnimation(Rocket::Core::TextureHandle handle, float advanceTime)
{
Assertion(handle != 0, "Invalid handle for setAnimationFrame");
if (!get_texture(handle)->is_animation) {
return;
}
auto tex = get_texture(handle);
generic_extras extras;
extras.draw = false; // We only want to advance the time but not actually render the animation
generic_anim_render(&tex->animation, advanceTime, -1, -1, false, &extras);
}
void RocketRenderingInterface::setHorizontalSwipeOffset(float value) {
horizontal_swipe_offset = value;
}
} // namespace scpui