Created mod UI API functions for imageview elements

This commit is contained in:
Mr-Wiseguy 2025-03-21 03:07:57 -04:00
parent 9284346fe2
commit d45c4f7236
10 changed files with 278 additions and 90 deletions

View File

@ -178,6 +178,7 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/ui_mod_menu.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_api.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_api_events.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_api_images.cpp
${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp
${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp

View File

@ -110,7 +110,9 @@ namespace recompui {
Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag);
Rml::ElementDocument* load_document(const std::filesystem::path& path);
Rml::ElementDocument* create_empty_document();
void queue_image_from_bytes(const std::string &src, const std::vector<char> &bytes);
void queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height);
void queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes);
void release_image(const std::string &src);
}

View File

@ -1,5 +1,8 @@
#include "recomp_ui.h"
#include "ui_helpers.h"
#include "ui_api_images.h"
#include "core/ui_context.h"
#include "core/ui_resource.h"
@ -25,80 +28,6 @@
using namespace recompui;
constexpr ResourceId root_element_id{ 0xFFFFFFFE };
// Helpers
ContextId get_context(uint8_t* rdram, recomp_context* ctx) {
uint32_t context_id = _arg<0, uint32_t>(rdram, ctx);
return ContextId{ .slot_id = context_id };
}
template <int arg_index>
ResourceId arg_resource_id(uint8_t* rdram, recomp_context* ctx) {
uint32_t slot_id = _arg<arg_index, uint32_t>(rdram, ctx);
return ResourceId{ .slot_id = slot_id };
}
template <int arg_index>
Element* arg_element(uint8_t* rdram, recomp_context* ctx, ContextId ui_context) {
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
if (resource == ResourceId::null()) {
return nullptr;
}
else if (resource == root_element_id) {
return ui_context.get_root_element();
}
return resource.as_element();
}
template <int arg_index>
Style* arg_style(uint8_t* rdram, recomp_context* ctx) {
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
if (resource == ResourceId::null()) {
return nullptr;
}
else if (resource == root_element_id) {
ContextId ui_context = recompui::get_current_context();
return ui_context.get_root_element();
}
return *resource;
}
template <int arg_index>
Color arg_color(uint8_t* rdram, recomp_context* ctx) {
PTR(u8) color_arg = _arg<arg_index, PTR(u8)>(rdram, ctx);
Color ret{};
ret.r = MEM_B(0, color_arg);
ret.g = MEM_B(1, color_arg);
ret.b = MEM_B(2, color_arg);
ret.a = MEM_B(3, color_arg);
return ret;
}
void return_resource(recomp_context* ctx, ResourceId resource) {
_return<uint32_t>(ctx, resource.slot_id);
}
void return_string(uint8_t* rdram, recomp_context* ctx, const std::string& ret) {
gpr addr = (reinterpret_cast<uint8_t*>(recomp::alloc(rdram, ret.size() + 1)) - rdram) + 0xFFFFFFFF80000000ULL;
for (size_t i = 0; i < ret.size(); i++) {
MEM_B(i, addr) = ret[i];
}
MEM_B(ret.size(), addr) = '\x00';
_return<PTR(char)>(ctx, addr);
}
// Contexts
void recompui_create_context(uint8_t* rdram, recomp_context* ctx) {
(void)rdram;
@ -915,4 +844,5 @@ void recompui::register_ui_exports() {
REGISTER_FUNC(recompui_get_input_text);
REGISTER_FUNC(recompui_set_input_text);
REGISTER_FUNC(recompui_register_callback);
register_ui_image_exports();
}

108
src/ui/ui_api_images.cpp Normal file
View File

@ -0,0 +1,108 @@
#include <mutex>
#include <unordered_set>
#include "recomp_ui.h"
#include "librecomp/overlays.hpp"
#include "librecomp/helpers.hpp"
#include "ultramodern/error_handling.hpp"
#include "ui_helpers.h"
#include "ui_api_images.h"
#include "elements/ui_image.h"
using namespace recompui;
struct {
std::mutex mutex;
std::unordered_set<uint32_t> textures{};
uint32_t textures_created = 0;
} TextureState;
const std::string mod_texture_prefix = "?/mod_api/";
static std::string get_texture_name(uint32_t texture_id) {
return mod_texture_prefix + std::to_string(texture_id);
}
static uint32_t get_new_texture_id() {
std::lock_guard lock{TextureState.mutex};
uint32_t cur_id = TextureState.textures_created++;
TextureState.textures.emplace(cur_id);
return cur_id;
}
static void release_texture(uint32_t texture_id) {
std::string texture_name = get_texture_name(texture_id);
std::lock_guard lock{TextureState.mutex};
if (TextureState.textures.erase(texture_id) == 0) {
recompui::message_box("Fatal error in mod - attempted to destroy texture that doesn't exist!");
assert(false);
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
}
recompui::release_image(texture_name);
}
void recompui_create_texture_rgba32(uint8_t* rdram, recomp_context* ctx) {
thread_local std::vector<char> data;
PTR(void) data_in = _arg<0, PTR(void)>(rdram, ctx);
uint32_t width = _arg<1, uint32_t>(rdram, ctx);
uint32_t height = _arg<2, uint32_t>(rdram, ctx);
uint32_t cur_id = get_new_texture_id();
// The size in bytes of the image's pixel data.
size_t size_bytes = width * height * 4 * sizeof(uint8_t);
data.resize(size_bytes);
// Byteswap copy the pixel data.
for (size_t i = 0; i < size_bytes; i++) {
data[i] = MEM_B(i, data_in);
}
// Create a texture name from the ID and queue its bytes.
std::string texture_name = get_texture_name(cur_id);
recompui::queue_image_from_bytes_rgba32(texture_name, data, width, height);
// Return the new texture ID.
_return(ctx, cur_id);
}
void recompui_destroy_texture(uint8_t* rdram, recomp_context* ctx) {
uint32_t texture_id = _arg<0, uint32_t>(rdram, ctx);
release_texture(texture_id);
}
void recompui_create_imageview(uint8_t* rdram, recomp_context* ctx) {
ContextId ui_context = get_context(rdram, ctx);
Element* parent = arg_element<1>(rdram, ctx, ui_context);
uint32_t texture_id = _arg<2, uint32_t>(rdram, ctx);
Element* ret = ui_context.create_element<Image>(parent, get_texture_name(texture_id));
return_resource(ctx, ret->get_resource_id());
}
void recompui_set_imageview_texture(uint8_t* rdram, recomp_context* ctx) {
Style* resource = arg_style<0>(rdram, ctx);
uint32_t texture_id = _arg<1, uint32_t>(rdram, ctx);
if (!resource->is_element()) {
recompui::message_box("Fatal error in mod - attempted to set texture of non-element");
assert(false);
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
}
Element* element = static_cast<Element*>(resource);
element->set_src(get_texture_name(texture_id));
}
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
void recompui::register_ui_image_exports() {
REGISTER_FUNC(recompui_create_texture_rgba32);
REGISTER_FUNC(recompui_destroy_texture);
REGISTER_FUNC(recompui_create_imageview);
REGISTER_FUNC(recompui_set_imageview_texture);
}

10
src/ui/ui_api_images.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef __UI_API_IMAGES_H__
#define __UI_API_IMAGES_H__
#include <cstdint>
namespace recompui {
void register_ui_image_exports();
}
#endif

87
src/ui/ui_helpers.h Normal file
View File

@ -0,0 +1,87 @@
#ifndef __UI_HELPERS_H__
#define __UI_HELPERS_H__
#include "librecomp/helpers.hpp"
#include "librecomp/addresses.hpp"
#include "elements/ui_element.h"
#include "elements/ui_types.h"
#include "core/ui_context.h"
#include "core/ui_resource.h"
namespace recompui {
constexpr ResourceId root_element_id{ 0xFFFFFFFE };
inline ContextId get_context(uint8_t* rdram, recomp_context* ctx) {
uint32_t context_id = _arg<0, uint32_t>(rdram, ctx);
return ContextId{ .slot_id = context_id };
}
template <int arg_index>
ResourceId arg_resource_id(uint8_t* rdram, recomp_context* ctx) {
uint32_t slot_id = _arg<arg_index, uint32_t>(rdram, ctx);
return ResourceId{ .slot_id = slot_id };
}
template <int arg_index>
Element* arg_element(uint8_t* rdram, recomp_context* ctx, ContextId ui_context) {
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
if (resource == ResourceId::null()) {
return nullptr;
}
else if (resource == root_element_id) {
return ui_context.get_root_element();
}
return resource.as_element();
}
template <int arg_index>
Style* arg_style(uint8_t* rdram, recomp_context* ctx) {
ResourceId resource = arg_resource_id<arg_index>(rdram, ctx);
if (resource == ResourceId::null()) {
return nullptr;
}
else if (resource == root_element_id) {
ContextId ui_context = recompui::get_current_context();
return ui_context.get_root_element();
}
return *resource;
}
template <int arg_index>
Color arg_color(uint8_t* rdram, recomp_context* ctx) {
PTR(u8) color_arg = _arg<arg_index, PTR(u8)>(rdram, ctx);
Color ret{};
ret.r = MEM_B(0, color_arg);
ret.g = MEM_B(1, color_arg);
ret.b = MEM_B(2, color_arg);
ret.a = MEM_B(3, color_arg);
return ret;
}
inline void return_resource(recomp_context* ctx, ResourceId resource) {
_return<uint32_t>(ctx, resource.slot_id);
}
inline void return_string(uint8_t* rdram, recomp_context* ctx, const std::string& ret) {
gpr addr = (reinterpret_cast<uint8_t*>(recomp::alloc(rdram, ret.size() + 1)) - rdram) + 0xFFFFFFFF80000000ULL;
for (size_t i = 0; i < ret.size(); i++) {
MEM_B(i, addr) = ret[i];
}
MEM_B(ret.size(), addr) = '\x00';
_return<PTR(char)>(ctx, addr);
}
}
#endif

View File

@ -441,7 +441,7 @@ void ModMenu::create_mod_list() {
const std::vector<char> &thumbnail = recomp::mods::get_mod_thumbnail(mod_details[mod_index].mod_id);
std::string thumbnail_name = generate_thumbnail_src_for_mod(mod_details[mod_index].mod_id);
if (!thumbnail.empty()) {
recompui::queue_image_from_bytes(thumbnail_name, thumbnail);
recompui::queue_image_from_bytes_file(thumbnail_name, thumbnail);
loaded_thumbnails.emplace(thumbnail_name);
}

View File

@ -72,7 +72,19 @@ T from_bytes_le(const char* input) {
return *reinterpret_cast<const T*>(input);
}
typedef std::pair<std::string, std::vector<char>> ImageFromBytes;
enum class ImageType {
File,
RGBA32
};
struct ImageFromBytes {
ImageType type;
// Dimensions only used for RGBA32 data. Files pull the size from the file data.
uint32_t width;
uint32_t height;
std::string name;
std::vector<char> bytes;
};
namespace recompui {
class RmlRenderInterface_RT64_impl : public Rml::RenderInterfaceCompatibility {
@ -138,7 +150,7 @@ class RmlRenderInterface_RT64_impl : public Rml::RenderInterfaceCompatibility {
bool scissor_enabled_ = false;
std::vector<std::unique_ptr<RT64::RenderBuffer>> stale_buffers_{};
moodycamel::ConcurrentQueue<ImageFromBytes> image_from_bytes_queue;
std::unordered_map<std::string, std::vector<char>> image_from_bytes_map;
std::unordered_map<std::string, ImageFromBytes> image_from_bytes_map;
public:
RmlRenderInterface_RT64_impl(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
interface_ = interface;
@ -415,11 +427,30 @@ public:
return true;
}
// TODO: This data copy can be avoided when RT64::TextureCache::loadTextureFromBytes's function is updated to only take a pointer and size as the input.
std::vector<uint8_t> data_copy(it->second.data(), it->second.data() + it->second.size());
RT64::Texture* texture = nullptr;
std::unique_ptr<RT64::RenderBuffer> texture_buffer;
ImageFromBytes& img = it->second;
copy_command_list_->begin();
RT64::Texture *texture = RT64::TextureCache::loadTextureFromBytes(device_, copy_command_list_.get(), data_copy, texture_buffer);
switch (img.type) {
case ImageType::RGBA32:
{
// Read the image header (two 32-bit values for width and height respectively).
uint32_t rowPitch = img.width * 4;
size_t byteCount = img.height * rowPitch;
texture = new RT64::Texture();
RT64::TextureCache::setRGBA32(texture, device_, copy_command_list_.get(), reinterpret_cast<const uint8_t*>(img.bytes.data()), byteCount, img.width, img.height, rowPitch, texture_buffer, nullptr);
}
break;
case ImageType::File:
{
// TODO: This data copy can be avoided when RT64::TextureCache::loadTextureFromBytes's function is updated to only take a pointer and size as the input.
std::vector<uint8_t> data_copy(img.bytes.data(), img.bytes.data() + img.bytes.size());
texture = RT64::TextureCache::loadTextureFromBytes(device_, copy_command_list_.get(), data_copy, texture_buffer);
}
break;
}
copy_command_list_->end();
copy_command_queue_->executeCommandLists(copy_command_list_.get(), copy_command_fence_.get());
copy_command_queue_->waitForCommandFence(copy_command_fence_.get());
@ -626,14 +657,21 @@ public:
list_ = nullptr;
}
void queue_image_from_bytes(const std::string &src, const std::vector<char> &bytes) {
image_from_bytes_queue.enqueue(ImageFromBytes(src, bytes));
void queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes) {
// Width and height aren't used for file images, so set them to 0.
image_from_bytes_queue.enqueue(ImageFromBytes(ImageType::File, 0, 0, src, bytes));
}
void queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height) {
image_from_bytes_queue.enqueue(ImageFromBytes(ImageType::RGBA32, width, height, src, bytes));
}
void flush_image_from_bytes_queue() {
ImageFromBytes image_from_bytes;
while (image_from_bytes_queue.try_dequeue(image_from_bytes)) {
image_from_bytes_map.emplace(image_from_bytes.first, std::move(image_from_bytes.second));
// We can move the name into the map since the name in the actual entry is no longer needed.
// After that, move the entry itself into the map.
image_from_bytes_map.emplace(std::move(image_from_bytes.name), std::move(image_from_bytes));
}
}
};
@ -669,8 +707,14 @@ void recompui::RmlRenderInterface_RT64::end(RT64::RenderCommandList* list, RT64:
impl->end(list, framebuffer);
}
void recompui::RmlRenderInterface_RT64::queue_image_from_bytes(const std::string &src, const std::vector<char> &bytes) {
void recompui::RmlRenderInterface_RT64::queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes) {
assert(static_cast<bool>(impl));
impl->queue_image_from_bytes(src, bytes);
}
impl->queue_image_from_bytes_file(src, bytes);
}
void recompui::RmlRenderInterface_RT64::queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height) {
assert(static_cast<bool>(impl));
impl->queue_image_from_bytes_rgba32(src, bytes, width, height);
}

View File

@ -2,6 +2,7 @@
#define __UI_RENDERER_H__
#include <memory>
#include "recomp_ui.h"
namespace RT64 {
struct RenderInterface;
@ -29,7 +30,8 @@ namespace recompui {
void start(RT64::RenderCommandList* list, int image_width, int image_height);
void end(RT64::RenderCommandList* list, RT64::RenderFramebuffer* framebuffer);
void queue_image_from_bytes(const std::string &src, const std::vector<char> &bytes);
void queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes);
void queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height);
};
} // namespace recompui

View File

@ -822,8 +822,12 @@ Rml::ElementDocument* recompui::create_empty_document() {
return ui_state->context->CreateDocument();
}
void recompui::queue_image_from_bytes(const std::string &src, const std::vector<char> &bytes) {
ui_state->render_interface.queue_image_from_bytes(src, bytes);
void recompui::queue_image_from_bytes_file(const std::string &src, const std::vector<char> &bytes) {
ui_state->render_interface.queue_image_from_bytes_file(src, bytes);
}
void recompui::queue_image_from_bytes_rgba32(const std::string &src, const std::vector<char> &bytes, uint32_t width, uint32_t height) {
ui_state->render_interface.queue_image_from_bytes_rgba32(src, bytes, width, height);
}
void recompui::release_image(const std::string &src) {