// // // 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(handle); } Rocket::Core::TextureHandle RocketRenderingInterface::get_texture_handle(RocketRenderingInterface::Texture* bitmap) { return reinterpret_cast(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(vertices)); geom->index_buffer = gr_create_buffer(BufferType::Index, BufferUsageHint::Static); gr_update_buffer_data(geom->index_buffer, num_indices * sizeof(int), reinterpret_cast(indices)); geom->num_elements = num_indices; geom->texture = get_texture(texture); return reinterpret_cast(geom); } void RocketRenderingInterface::RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation) { TRACE_SCOPE(tracing::RocketRenderCompiledGeometry); GR_DEBUG_SCOPE("libRocket::RenderCompiledGeometry"); auto geom = reinterpret_cast(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(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 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 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 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