mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-04-07 05:56:53 +02:00
Created mod UI API functions for imageview elements
This commit is contained in:
parent
9284346fe2
commit
d45c4f7236
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
108
src/ui/ui_api_images.cpp
Normal 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
10
src/ui/ui_api_images.h
Normal 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
87
src/ui/ui_helpers.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user