Refactor viewport fit handling; use game viewport for RmlUi

This commit is contained in:
Luke Street
2026-05-05 23:13:31 -06:00
parent 7c5242d33d
commit 4cd8d2f009
16 changed files with 560 additions and 230 deletions
+5 -5
View File
@@ -83,19 +83,19 @@ void GXPopDebugGroup();
void GXInsertDebugMarker(const char* label);
typedef enum _AuroraViewportPolicy {
AURORA_VIEWPORT_FIT = 0, // Scale logical viewport to fit
AURORA_VIEWPORT_STRETCH = 1, // Stretch logical viewport to fit (widescreen enabled)
AURORA_VIEWPORT_NATIVE = 2, // Use native framebuffer resolution
AURORA_VIEWPORT_FIT = 0, // Preserve logical aspect in the content framebuffer
AURORA_VIEWPORT_STRETCH = 1, // Match content framebuffer aspect to the native surface
AURORA_VIEWPORT_NATIVE = 2, // Use active framebuffer pixels directly
} AuroraViewportPolicy;
/**
* Configures how GXSetViewport/GXSetScissor parameters are applied to the actual render viewport.
* Configures content framebuffer sizing and how GXSetViewport/GXSetScissor parameters are applied to rendering.
* When AURORA_VIEWPORT_NATIVE is used, GXSetTexCopySrc/GXSetTexCopyDst will use native framebuffer resolution.
*/
void AuroraSetViewportPolicy(AuroraViewportPolicy policy);
/**
* Retrieves the current render size based on the framebuffer size and viewport policy.
* Retrieves the current content framebuffer size.
*/
void AuroraGetRenderSize(u32* width, u32* height);
+13 -43
View File
@@ -69,36 +69,6 @@ constexpr std::array<AuroraBackend, 0> PreferredBackendOrder{};
bool g_initialFrame = false;
#ifdef AURORA_ENABLE_GX
gfx::Viewport calculate_present_viewport(uint32_t surface_width, uint32_t surface_height, uint32_t content_width,
uint32_t content_height) {
if (surface_width == 0 || surface_height == 0 || content_width == 0 || content_height == 0) {
return {};
}
uint32_t viewport_width = surface_width;
uint32_t viewport_height = std::min<uint32_t>(
surface_height, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(static_cast<double>(viewport_width) *
static_cast<double>(content_height) /
static_cast<double>(content_width)))));
if (viewport_height == surface_height) {
viewport_width = std::min<uint32_t>(
surface_width, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(static_cast<double>(viewport_height) *
static_cast<double>(content_width) /
static_cast<double>(content_height)))));
}
return {
.left = static_cast<float>((surface_width - viewport_width) / 2),
.top = static_cast<float>((surface_height - viewport_height) / 2),
.width = static_cast<float>(viewport_width),
.height = static_cast<float>(viewport_height),
.znear = 0.f,
.zfar = 1.f,
};
}
#endif
AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexcept {
g_config = config;
Log.info("Aurora initializing");
@@ -270,19 +240,18 @@ void end_frame() noexcept {
auto encoder = g_device.CreateCommandEncoder(&encoderDescriptor);
gfx::end_frame(encoder);
gfx::render(encoder);
const auto viewport = calculate_present_viewport(webgpu::g_graphicsConfig.surfaceConfiguration.width,
webgpu::g_graphicsConfig.surfaceConfiguration.height,
webgpu::g_frameBuffer.size.width, webgpu::g_frameBuffer.size.height);
const auto& presentSource = webgpu::present_source();
auto viewport = webgpu::calculate_present_viewport(webgpu::g_graphicsConfig.surfaceConfiguration.width,
webgpu::g_graphicsConfig.surfaceConfiguration.height,
presentSource.size.width, presentSource.size.height);
wgpu::BindGroup presentBindGroup = webgpu::g_CopyBindGroup;
#if AURORA_ENABLE_RMLUI
if (rmlui::is_initialized()) {
rmlui::render(encoder, g_currentView,
{
.width = webgpu::g_graphicsConfig.surfaceConfiguration.width,
.height = webgpu::g_graphicsConfig.surfaceConfiguration.height,
.depthOrArrayLayers = 1,
},
viewport);
} else
const auto rmlOutput = rmlui::render(encoder, viewport);
if (rmlOutput.texture != nullptr) {
presentBindGroup = rmlOutput.copyBindGroup;
}
}
#endif
{
const std::array attachments{
@@ -300,7 +269,7 @@ void end_frame() noexcept {
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
// Copy EFB -> XFB (swapchain)
pass.SetPipeline(webgpu::g_CopyPipeline);
pass.SetBindGroup(0, webgpu::g_CopyBindGroup, 0, nullptr);
pass.SetBindGroup(0, presentBindGroup, 0, nullptr);
pass.SetViewport(viewport.left, viewport.top, viewport.width, viewport.height, viewport.znear, viewport.zfar);
pass.Draw(3);
@@ -320,7 +289,8 @@ void end_frame() noexcept {
.colorAttachments = attachments.data(),
};
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
pass.SetViewport(viewport.left, viewport.top, viewport.width, viewport.height, viewport.znear, viewport.zfar);
pass.SetViewport(0.f, 0.f, static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.width),
static_cast<float>(webgpu::g_graphicsConfig.surfaceConfiguration.height), 0.f, 1.f);
imgui::render(pass);
pass.End();
}
+6 -24
View File
@@ -1,7 +1,5 @@
#include "dolphin/gx/GXAurora.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include "__gx.h"
@@ -10,7 +8,6 @@
#include "../../gfx/common.hpp"
#include "../../gx/fifo.hpp"
#include "../vi/vi_internal.hpp"
static void GXWriteString(const char* label) {
auto length = strlen(label);
@@ -36,33 +33,18 @@ void GXInsertDebugMarker(const char* label) {
GXWriteString(label);
}
void AuroraSetViewportPolicy(AuroraViewportPolicy policy) { g_gxState.viewportPolicy = policy; }
void AuroraSetViewportPolicy(AuroraViewportPolicy policy) {
g_gxState.viewportPolicy = policy;
aurora::window::set_frame_buffer_aspect_fit(policy == AURORA_VIEWPORT_FIT);
}
void AuroraGetRenderSize(u32* width, u32* height) {
const auto windowSize = aurora::window::get_window_size();
u32 renderWidth = windowSize.fb_width;
u32 renderHeight = windowSize.fb_height;
if (g_gxState.viewportPolicy == AURORA_VIEWPORT_FIT) {
const auto efbSize = aurora::vi::configured_fb_size();
if (efbSize.x != 0 && efbSize.y != 0 && renderWidth != 0 && renderHeight != 0) {
const double targetAspect = static_cast<double>(renderWidth) / static_cast<double>(renderHeight);
const double contentAspect = static_cast<double>(efbSize.x) / static_cast<double>(efbSize.y);
if (targetAspect > contentAspect) {
renderWidth =
std::max<u32>(1u, static_cast<u32>(std::lround(static_cast<double>(renderHeight) * contentAspect)));
} else {
renderHeight =
std::max<u32>(1u, static_cast<u32>(std::lround(static_cast<double>(renderWidth) / contentAspect)));
}
}
}
if (width != nullptr) {
*width = renderWidth;
*width = windowSize.fb_width;
}
if (height != nullptr) {
*height = renderHeight;
*height = windowSize.fb_height;
}
}
+17 -9
View File
@@ -9,20 +9,28 @@
namespace aurora::vi {
std::optional<GXRenderModeObj> g_renderMode;
void configure(const GXRenderModeObj* rm) noexcept {
if (rm == nullptr) {
g_renderMode.reset();
return;
}
g_renderMode = *rm;
}
Vec2<uint32_t> configured_fb_size() noexcept {
Vec2<uint32_t> render_mode_size() noexcept {
if (!g_renderMode) {
return {640, 480};
}
return {g_renderMode->fbWidth, g_renderMode->efbHeight};
}
void configure(const GXRenderModeObj* rm) noexcept {
const auto oldSize = render_mode_size();
if (rm == nullptr) {
g_renderMode.reset();
} else {
g_renderMode = *rm;
}
if (render_mode_size() != oldSize) {
window::request_frame_buffer_resize();
}
}
Vec2<uint32_t> configured_fb_size() noexcept {
return render_mode_size();
}
} // namespace aurora::vi
extern "C" {
+2 -14
View File
@@ -1,6 +1,7 @@
#pragma once
#include "../internal.hpp"
#include "../webgpu/gpu.hpp"
#include <cstdint>
#include <cstdlib>
@@ -208,20 +209,7 @@ struct ClipRect {
bool operator!=(const ClipRect& rhs) const { return !(*this == rhs); }
};
struct Viewport {
float left;
float top;
float width;
float height;
float znear;
float zfar;
bool operator==(const Viewport& rhs) const {
return left == rhs.left && top == rhs.top && width == rhs.width && height == rhs.height && znear == rhs.znear &&
zfar == rhs.zfar;
}
bool operator!=(const Viewport& rhs) const { return !(*this == rhs); }
};
using webgpu::Viewport;
struct TextureRef;
using TextureHandle = std::shared_ptr<TextureRef>;
+12 -38
View File
@@ -246,31 +246,6 @@ Vec2<uint32_t> logical_fb_size() noexcept {
return gfx::is_offscreen() ? gfx::get_render_target_size() : vi::configured_fb_size();
}
std::tuple<float, float, float, float> calculate_inner_box(const int targetWidth, const int targetHeight,
const int logicalFbWidth, const int logicalFbHeight) noexcept {
if (g_gxState.viewportPolicy == AURORA_VIEWPORT_STRETCH) {
return {
0.0f,
0.0f,
static_cast<float>(targetWidth),
static_cast<float>(targetHeight),
};
} else {
const auto scale = std::min(static_cast<float>(targetWidth) / static_cast<float>(logicalFbWidth),
static_cast<float>(targetHeight) / static_cast<float>(logicalFbHeight));
const auto offsetX =
std::trunc((static_cast<float>(targetWidth) - static_cast<float>(logicalFbWidth) * scale) * 0.5f);
const auto offsetY =
std::trunc((static_cast<float>(targetHeight) - static_cast<float>(logicalFbHeight) * scale) * 0.5f);
return {
offsetX,
offsetY,
static_cast<float>(targetWidth) - 2.0f * offsetX,
static_cast<float>(targetHeight) - 2.0f * offsetY,
};
}
}
gfx::Viewport map_logical_viewport(const gfx::Viewport& logicalViewport) noexcept {
if (g_gxState.viewportPolicy == AURORA_VIEWPORT_NATIVE) {
return logicalViewport;
@@ -282,14 +257,13 @@ gfx::Viewport map_logical_viewport(const gfx::Viewport& logicalViewport) noexcep
return logicalViewport;
}
const auto [offsetX, offsetY, innerWidth, innerHeight] =
calculate_inner_box(targetWidth, targetHeight, logicalFbWidth, logicalFbHeight);
const float scaleX = static_cast<float>(targetWidth) / static_cast<float>(logicalFbWidth);
const float scaleY = static_cast<float>(targetHeight) / static_cast<float>(logicalFbHeight);
return {
.left = offsetX + (logicalViewport.left / static_cast<float>(logicalFbWidth)) * innerWidth,
.top = offsetY + (logicalViewport.top / static_cast<float>(logicalFbHeight)) * innerHeight,
.width = (logicalViewport.width / static_cast<float>(logicalFbWidth)) * innerWidth,
.height = (logicalViewport.height / static_cast<float>(logicalFbHeight)) * innerHeight,
.left = logicalViewport.left * scaleX,
.top = logicalViewport.top * scaleY,
.width = logicalViewport.width * scaleX,
.height = logicalViewport.height * scaleY,
.znear = logicalViewport.znear,
.zfar = logicalViewport.zfar,
};
@@ -306,13 +280,13 @@ gfx::ClipRect map_logical_scissor(const gfx::ClipRect& logicalScissor) noexcept
return logicalScissor;
}
const auto [offsetX, offsetY, innerWidth, innerHeight] =
calculate_inner_box(targetWidth, targetHeight, logicalFbWidth, logicalFbHeight);
const float scaleX = static_cast<float>(targetWidth) / static_cast<float>(logicalFbWidth);
const float scaleY = static_cast<float>(targetHeight) / static_cast<float>(logicalFbHeight);
const float left = offsetX + (static_cast<float>(logicalScissor.x) / static_cast<float>(logicalFbWidth)) * innerWidth;
const float top = offsetY + (static_cast<float>(logicalScissor.y) / static_cast<float>(logicalFbHeight)) * innerHeight;
const float right = offsetX + (static_cast<float>(logicalScissor.x + logicalScissor.width) / static_cast<float>(logicalFbWidth)) * innerWidth;
const float bottom = offsetY + (static_cast<float>(logicalScissor.y + logicalScissor.height) / static_cast<float>(logicalFbHeight)) * innerHeight;
const float left = static_cast<float>(logicalScissor.x) * scaleX;
const float top = static_cast<float>(logicalScissor.y) * scaleY;
const float right = static_cast<float>(logicalScissor.x + logicalScissor.width) * scaleX;
const float bottom = static_cast<float>(logicalScissor.y + logicalScissor.height) * scaleY;
const auto mappedLeft = std::clamp(static_cast<int32_t>(std::floor(left)), 0, static_cast<int32_t>(targetWidth));
const auto mappedTop = std::clamp(static_cast<int32_t>(std::floor(top)), 0, static_cast<int32_t>(targetHeight));
+332 -17
View File
@@ -13,21 +13,292 @@
#include "webgpu/gpu.hpp"
namespace aurora::rmlui {
Rml::Context* g_context = nullptr;
FileInterface_SDL* g_fileInterface = nullptr;
namespace {
Module Log("aurora::rmlui");
constexpr size_t MaxTrackedTouches = 16;
struct TrackedTouch {
SDL_FingerID id = 0;
Rml::Vector2f position;
bool active = false;
};
uint32_t s_pressedMouseButtons = 0;
std::array<TrackedTouch, MaxTrackedTouches> s_trackedTouches{};
webgpu::TextureWithSampler s_renderTarget;
wgpu::BindGroup s_renderTargetCopyBindGroup;
WebGPURenderInterface* get_render_interface() noexcept {
return static_cast<WebGPURenderInterface*>(Backend::GetRenderInterface()); // NOLINT(*-pro-type-static-cast-downcast)
}
Rml::Vector2i dimensions_from_viewport(const gfx::Viewport& viewport) noexcept {
return {
std::max(1, static_cast<int>(std::lround(viewport.width))),
std::max(1, static_cast<int>(std::lround(viewport.height))),
};
}
Rml::Vector2i presentation_dimensions_from_window_size(const AuroraWindowSize& size) noexcept {
const auto viewport =
webgpu::calculate_present_viewport(size.native_fb_width, size.native_fb_height, size.fb_width, size.fb_height);
return dimensions_from_viewport(viewport);
}
float density_ratio_for_window(const AuroraWindowSize& size) noexcept {
if (size.width == 0 || size.height == 0 || size.native_fb_width == 0 || size.native_fb_height == 0) {
return 1.f;
}
return std::min(static_cast<float>(size.native_fb_width) / static_cast<float>(size.width),
static_cast<float>(size.native_fb_height) / static_cast<float>(size.height));
}
void sync_context_metrics(Rml::Vector2i dimensions) noexcept {
if (g_context == nullptr || dimensions.x <= 0 || dimensions.y <= 0) {
return;
}
if (g_context->GetDimensions() != dimensions) {
g_context->SetDimensions(dimensions);
}
const float ratio = density_ratio_for_window(window::get_window_size());
if (g_context->GetDensityIndependentPixelRatio() != ratio) {
g_context->SetDensityIndependentPixelRatio(ratio);
}
}
void ensure_render_target(Rml::Vector2i dimensions) noexcept {
if (dimensions.x <= 0 || dimensions.y <= 0) {
return;
}
const auto width = static_cast<uint32_t>(dimensions.x);
const auto height = static_cast<uint32_t>(dimensions.y);
if (s_renderTarget.view && s_renderTarget.size.width == width && s_renderTarget.size.height == height) {
return;
}
s_renderTarget = webgpu::create_render_texture(width, height, false);
s_renderTargetCopyBindGroup = webgpu::create_copy_bind_group(s_renderTarget);
}
void copy_game_to_render_target(const wgpu::CommandEncoder& encoder) noexcept {
if (!s_renderTarget.view) {
return;
}
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = s_renderTarget.view,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
},
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "RmlUi game frame copy pass",
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
};
const auto pass = encoder.BeginRenderPass(&renderPassDescriptor);
pass.SetPipeline(webgpu::g_CopyPipeline);
pass.SetBindGroup(0, webgpu::g_CopyBindGroup, 0, nullptr);
pass.SetViewport(0.f, 0.f, static_cast<float>(s_renderTarget.size.width),
static_cast<float>(s_renderTarget.size.height), 0.f, 1.f);
pass.Draw(3);
pass.End();
}
struct MappedPoint {
Rml::Vector2f position;
bool inside = false;
};
MappedPoint map_native_point_to_content(float nativeX, float nativeY) noexcept {
if (g_context == nullptr) {
return {};
}
const auto size = window::get_window_size();
const Rml::Vector2i contentSize = g_context->GetDimensions();
if (size.native_fb_width == 0 || size.native_fb_height == 0 || contentSize.x <= 0 || contentSize.y <= 0) {
return {};
}
const auto viewport =
webgpu::calculate_present_viewport(size.native_fb_width, size.native_fb_height,
static_cast<uint32_t>(contentSize.x), static_cast<uint32_t>(contentSize.y));
const float right = viewport.left + viewport.width;
const float bottom = viewport.top + viewport.height;
if (viewport.width <= 0.f || viewport.height <= 0.f || nativeX < viewport.left || nativeY < viewport.top ||
nativeX >= right || nativeY >= bottom) {
return {};
}
return {
.position =
{
(nativeX - viewport.left) * static_cast<float>(contentSize.x) / viewport.width,
(nativeY - viewport.top) * static_cast<float>(contentSize.y) / viewport.height,
},
.inside = true,
};
}
MappedPoint map_window_point_to_content(float windowX, float windowY) noexcept {
const auto size = window::get_window_size();
if (size.width == 0 || size.height == 0) {
return {};
}
return map_native_point_to_content(
windowX * static_cast<float>(size.native_fb_width) / static_cast<float>(size.width),
windowY * static_cast<float>(size.native_fb_height) / static_cast<float>(size.height));
}
MappedPoint map_touch_point_to_content(const SDL_TouchFingerEvent& event) noexcept {
const auto size = window::get_window_size();
return map_native_point_to_content(event.x * static_cast<float>(size.native_fb_width),
event.y * static_cast<float>(size.native_fb_height));
}
int rounded_content_coord(float value) noexcept { return static_cast<int>(std::floor(value)); }
bool mouse_button_tracked(uint8_t button) noexcept {
return button < 32 && (s_pressedMouseButtons & (1u << button)) != 0;
}
void set_mouse_button_tracked(uint8_t button, bool tracked) noexcept {
if (button >= 32) {
return;
}
const uint32_t mask = 1u << button;
if (tracked) {
s_pressedMouseButtons |= mask;
} else {
s_pressedMouseButtons &= ~mask;
}
}
TrackedTouch* find_tracked_touch(SDL_FingerID id) noexcept {
for (auto& touch : s_trackedTouches) {
if (touch.active && touch.id == id) {
return &touch;
}
}
return nullptr;
}
TrackedTouch* find_free_touch() noexcept {
for (auto& touch : s_trackedTouches) {
if (!touch.active) {
return &touch;
}
}
return nullptr;
}
Rml::TouchList touch_list(SDL_FingerID id, Rml::Vector2f position) {
return {Rml::Touch{static_cast<Rml::TouchId>(id), position}};
}
void handle_mouse_motion(const SDL_MouseMotionEvent& motion) noexcept {
const MappedPoint mapped = map_window_point_to_content(motion.x, motion.y);
if (!mapped.inside) {
g_context->ProcessMouseLeave();
return;
}
g_context->ProcessMouseMove(rounded_content_coord(mapped.position.x), rounded_content_coord(mapped.position.y),
RmlSDL::GetKeyModifierState());
}
void handle_mouse_button_down(const SDL_MouseButtonEvent& button) noexcept {
const MappedPoint mapped = map_window_point_to_content(button.x, button.y);
if (!mapped.inside) {
g_context->ProcessMouseLeave();
return;
}
g_context->ProcessMouseMove(rounded_content_coord(mapped.position.x), rounded_content_coord(mapped.position.y),
RmlSDL::GetKeyModifierState());
g_context->ProcessMouseButtonDown(RmlSDL::ConvertMouseButton(button.button), RmlSDL::GetKeyModifierState());
set_mouse_button_tracked(button.button, true);
SDL_CaptureMouse(true);
}
void handle_mouse_button_up(const SDL_MouseButtonEvent& button) noexcept {
if (!mouse_button_tracked(button.button)) {
return;
}
const auto mapped = map_window_point_to_content(button.x, button.y);
if (mapped.inside) {
g_context->ProcessMouseMove(rounded_content_coord(mapped.position.x), rounded_content_coord(mapped.position.y),
RmlSDL::GetKeyModifierState());
} else {
g_context->ProcessMouseLeave();
}
g_context->ProcessMouseButtonUp(RmlSDL::ConvertMouseButton(button.button), RmlSDL::GetKeyModifierState());
set_mouse_button_tracked(button.button, false);
if (s_pressedMouseButtons == 0) {
SDL_CaptureMouse(false);
}
}
void handle_mouse_wheel(const SDL_MouseWheelEvent& wheel) noexcept {
float mouseX = 0.f;
float mouseY = 0.f;
SDL_GetMouseState(&mouseX, &mouseY);
if (!map_window_point_to_content(mouseX, mouseY).inside) {
g_context->ProcessMouseLeave();
return;
}
g_context->ProcessMouseWheel(Rml::Vector2f{wheel.x, -wheel.y}, RmlSDL::GetKeyModifierState());
}
void handle_touch_down(const SDL_TouchFingerEvent& finger) noexcept {
if (find_tracked_touch(finger.fingerID) != nullptr) {
return;
}
const auto mapped = map_touch_point_to_content(finger);
if (!mapped.inside) {
return;
}
auto* tracked = find_free_touch();
if (tracked == nullptr) {
return;
}
*tracked = {
.id = finger.fingerID,
.position = mapped.position,
.active = true,
};
g_context->ProcessTouchStart(touch_list(finger.fingerID, mapped.position), RmlSDL::GetKeyModifierState());
}
void handle_touch_motion(const SDL_TouchFingerEvent& finger) noexcept {
auto* tracked = find_tracked_touch(finger.fingerID);
if (tracked == nullptr) {
return;
}
const auto mapped = map_touch_point_to_content(finger);
if (!mapped.inside) {
return;
}
tracked->position = mapped.position;
g_context->ProcessTouchMove(touch_list(finger.fingerID, mapped.position), RmlSDL::GetKeyModifierState());
}
void handle_touch_up(const SDL_TouchFingerEvent& finger) noexcept {
auto* tracked = find_tracked_touch(finger.fingerID);
if (tracked == nullptr) {
return;
}
const auto position = tracked->position;
*tracked = {};
g_context->ProcessTouchEnd(touch_list(finger.fingerID, position), RmlSDL::GetKeyModifierState());
}
} // namespace
Rml::Context* g_context = nullptr;
FileInterface_SDL* g_fileInterface = nullptr;
void initialize(const AuroraWindowSize& size) noexcept {
int width = static_cast<int>(size.native_fb_width);
int height = static_cast<int>(size.native_fb_height);
if (!Backend::Initialize("Aurora RmlUi Backend", width, height, false)) {
const Rml::Vector2i dim = presentation_dimensions_from_window_size(size);
if (!Backend::Initialize("Aurora RmlUi Backend", dim.x, dim.y, false)) {
Log.error("Failed to initialize RmlUI Backend!");
return;
}
@@ -39,16 +310,17 @@ void initialize(const AuroraWindowSize& size) noexcept {
Rml::SetRenderInterface(renderInterface);
Rml::SetFileInterface(g_fileInterface);
renderInterface->SetWindowSize({width, height});
renderInterface->SetWindowSize(dim);
renderInterface->SetRenderTargetFormat(webgpu::g_graphicsConfig.surfaceConfiguration.format);
renderInterface->CreateDeviceObjects();
Rml::Initialise();
g_context = Rml::CreateContext("main", Rml::Vector2i(width, height));
g_context = Rml::CreateContext("main", dim);
if (!g_context) {
if (g_context) {
sync_context_metrics(dim);
} else {
Log.error("Failed to initialize RmlUI Context!");
Rml::Shutdown();
Backend::Shutdown();
}
@@ -70,32 +342,73 @@ void handle_event(SDL_Event& event) noexcept {
return;
}
// RmlSDL::InputEventHandler only passes through -wheel.y, but we want horizontal scrolling too
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
g_context->ProcessMouseWheel(Rml::Vector2f{event.wheel.x, -event.wheel.y}, RmlSDL::GetKeyModifierState());
switch (event.type) {
case SDL_EVENT_MOUSE_MOTION:
handle_mouse_motion(event.motion);
return;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
handle_mouse_button_down(event.button);
return;
case SDL_EVENT_MOUSE_BUTTON_UP:
handle_mouse_button_up(event.button);
return;
case SDL_EVENT_MOUSE_WHEEL:
handle_mouse_wheel(event.wheel);
return;
case SDL_EVENT_FINGER_DOWN:
handle_touch_down(event.tfinger);
return;
case SDL_EVENT_FINGER_MOTION:
handle_touch_motion(event.tfinger);
return;
case SDL_EVENT_FINGER_UP:
case SDL_EVENT_FINGER_CANCELED:
handle_touch_up(event.tfinger);
return;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
sync_context_metrics(presentation_dimensions_from_window_size(window::get_window_size()));
return;
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
g_context->ProcessMouseLeave();
return;
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
sync_context_metrics(presentation_dimensions_from_window_size(window::get_window_size()));
return;
default:
break;
}
RmlSDL::InputEventHandler(g_context, window::get_sdl_window(), event);
}
void render(const wgpu::CommandEncoder& encoder, const wgpu::TextureView& outputView, const wgpu::Extent3D& size,
const gfx::Viewport& viewport) noexcept {
RenderOutput render(const wgpu::CommandEncoder& encoder, const webgpu::Viewport& presentViewport) noexcept {
if (g_context == nullptr) {
return;
return {};
}
const Rml::Vector2i dim = dimensions_from_viewport(presentViewport);
ensure_render_target(dim);
if (!s_renderTarget.view) {
return {};
}
copy_game_to_render_target(encoder);
sync_context_metrics(dim);
g_context->Update();
auto* renderInterface = get_render_interface();
renderInterface->SetWindowSize(g_context->GetDimensions());
renderInterface->BeginFrame(encoder, outputView, size, viewport);
renderInterface->BeginFrame(encoder, s_renderTarget);
Backend::BeginFrame();
g_context->Render();
Backend::PresentFrame();
renderInterface->EndFrame();
return {
.texture = &s_renderTarget,
.copyBindGroup = s_renderTargetCopyBindGroup,
};
}
void shutdown() noexcept {
@@ -106,5 +419,7 @@ void shutdown() noexcept {
Rml::Shutdown();
Backend::Shutdown();
g_context = nullptr;
s_renderTarget = {};
s_renderTargetCopyBindGroup = {};
}
} // namespace aurora::rmlui
+7 -3
View File
@@ -1,16 +1,20 @@
#pragma once
#include "aurora/aurora.h"
#include "gfx/common.hpp"
#include "webgpu/gpu.hpp"
#include <SDL3/SDL_events.h>
#include <aurora/rmlui.hpp>
#include <dawn/webgpu_cpp.h>
namespace aurora::rmlui {
struct RenderOutput {
const webgpu::TextureWithSampler* texture = nullptr;
wgpu::BindGroup copyBindGroup;
};
void initialize(const AuroraWindowSize& size) noexcept;
void handle_event(SDL_Event& event) noexcept;
void render(const wgpu::CommandEncoder& encoder, const wgpu::TextureView& outputView, const wgpu::Extent3D& size,
const gfx::Viewport& viewport) noexcept;
RenderOutput render(const wgpu::CommandEncoder& encoder, const webgpu::Viewport& presentViewport) noexcept;
void shutdown() noexcept;
} // namespace aurora::rmlui
+27 -6
View File
@@ -6,6 +6,7 @@
#include "../logging.hpp"
#include "../window.hpp"
#include "../webgpu/gpu.hpp"
namespace aurora::rmlui {
namespace {
@@ -51,16 +52,36 @@ void SystemInterface_Aurora::ActivateKeyboard(Rml::Vector2f caret_position, floa
}
const AuroraWindowSize size = window::get_window_size();
if (size.native_fb_width == 0 || size.native_fb_height == 0 || size.width == 0 || size.height == 0) {
if (size.native_fb_width == 0 || size.native_fb_height == 0 || size.width == 0 || size.height == 0 ||
size.fb_width == 0 || size.fb_height == 0) {
return;
}
const float scaleX = static_cast<float>(size.width) / static_cast<float>(size.native_fb_width);
const float scaleY = static_cast<float>(size.height) / static_cast<float>(size.native_fb_height);
const float inputLineHeight = std::max(line_height * scaleY, 1.0f);
Rml::Vector2i contentSize{static_cast<int>(size.fb_width), static_cast<int>(size.fb_height)};
if (const Rml::Context* context = get_context()) {
contentSize = context->GetDimensions();
}
if (contentSize.x <= 0 || contentSize.y <= 0) {
return;
}
const auto viewport = webgpu::calculate_present_viewport(size.native_fb_width, size.native_fb_height,
static_cast<uint32_t>(contentSize.x),
static_cast<uint32_t>(contentSize.y));
if (viewport.width <= 0.f || viewport.height <= 0.f) {
return;
}
const float nativeToWindowX = static_cast<float>(size.width) / static_cast<float>(size.native_fb_width);
const float nativeToWindowY = static_cast<float>(size.height) / static_cast<float>(size.native_fb_height);
const float contentToNativeX = viewport.width / static_cast<float>(contentSize.x);
const float contentToNativeY = viewport.height / static_cast<float>(contentSize.y);
const float inputLineHeight = std::max(line_height * contentToNativeY * nativeToWindowY, 1.0f);
const float nativeCaretX = viewport.left + caret_position.x * contentToNativeX;
const float nativeCaretY = viewport.top + caret_position.y * contentToNativeY;
const SDL_Rect rect = {
.x = static_cast<int>(std::lround(caret_position.x * scaleX)),
.y = static_cast<int>(std::lround((caret_position.y * scaleY) + inputLineHeight)),
.x = static_cast<int>(std::lround(nativeCaretX * nativeToWindowX)),
.y = static_cast<int>(std::lround(nativeCaretY * nativeToWindowY + inputLineHeight)),
.w = 1,
.h = static_cast<int>(std::lround(inputLineHeight)),
};
+25 -55
View File
@@ -721,11 +721,14 @@ void WebGPURenderInterface::EnsureRenderTarget(RenderTarget& target, const char*
};
target.texture = webgpu::g_device.CreateTexture(&textureDesc);
target.view = target.texture.CreateView(nullptr);
target.bindGroup = CreateImageBindGroup(target.view);
}
wgpu::BindGroup WebGPURenderInterface::CreateImageBindGroup(const wgpu::TextureView& view) const {
const std::array bindGroupEntries{
wgpu::BindGroupEntry{
.binding = 0,
.textureView = target.view,
.textureView = view,
},
};
const wgpu::BindGroupDescriptor bindGroupDesc{
@@ -733,7 +736,7 @@ void WebGPURenderInterface::EnsureRenderTarget(RenderTarget& target, const char*
.entryCount = bindGroupEntries.size(),
.entries = bindGroupEntries.data(),
};
target.bindGroup = webgpu::g_device.CreateBindGroup(&bindGroupDesc);
return webgpu::g_device.CreateBindGroup(&bindGroupDesc);
}
void WebGPURenderInterface::EnsureFrameTargets(const wgpu::Extent3D& size) {
@@ -741,7 +744,6 @@ void WebGPURenderInterface::EnsureFrameTargets(const wgpu::Extent3D& size) {
m_layers.resize(1);
}
EnsureRenderTarget(m_layers[0], "RmlUi Base Layer", size);
EnsureRenderTarget(m_postprocessTargets[0], "RmlUi Postprocess A", size);
EnsureRenderTarget(m_postprocessTargets[1], "RmlUi Postprocess B", size);
EnsureRenderTarget(m_postprocessTargets[2], "RmlUi Postprocess C", size);
@@ -1041,38 +1043,30 @@ void WebGPURenderInterface::RenderFilters(Rml::Span<const Rml::CompiledFilterHan
EndActivePass();
}
void WebGPURenderInterface::BeginFrame(const wgpu::CommandEncoder& encoder, const wgpu::TextureView& outputView,
const wgpu::Extent3D& size, const gfx::Viewport& viewport) {
void WebGPURenderInterface::BeginFrame(const wgpu::CommandEncoder& encoder,
const webgpu::TextureWithSampler& target) {
m_encoder = encoder;
m_outputView = outputView;
m_frameSize = size;
m_viewport = viewport;
m_frameSize = target.size;
m_viewport = {
.left = 0.f,
.top = 0.f,
.width = static_cast<float>(target.size.width),
.height = static_cast<float>(target.size.height),
.znear = 0.f,
.zfar = 1.f,
};
m_nextLayer = 1;
m_layerStack = {0};
m_activeLayer = 0;
NewFrame();
EnsureFrameTargets(size);
const std::array copyAttachments{
wgpu::RenderPassColorAttachment{
.view = m_layers[0].view,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = {0.f, 0.f, 0.f, 0.f},
},
EnsureFrameTargets(target.size);
m_layers[0] = {
.texture = target.texture,
.view = target.view,
.bindGroup = CreateImageBindGroup(target.view),
.size = target.size,
};
const wgpu::RenderPassDescriptor copyPassDesc{
.label = "RmlUi game frame copy pass",
.colorAttachmentCount = copyAttachments.size(),
.colorAttachments = copyAttachments.data(),
};
m_pass = m_encoder.BeginRenderPass(&copyPassDesc);
ApplyViewport();
m_pass.SetPipeline(webgpu::g_CopyPipeline);
m_pass.SetBindGroup(0, webgpu::g_CopyBindGroup, 0, nullptr);
m_pass.Draw(3);
EndActivePass();
BeginRenderTargetPass(m_layers[0].view, wgpu::LoadOp::Load, "RmlUi base layer pass", true);
}
@@ -1080,35 +1074,11 @@ void WebGPURenderInterface::BeginFrame(const wgpu::CommandEncoder& encoder, cons
void WebGPURenderInterface::EndFrame() {
EndActivePass();
const wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{
.view = GetClipMaskStencilView(m_frameSize),
.stencilLoadOp = wgpu::LoadOp::Load,
.stencilStoreOp = wgpu::StoreOp::Store,
};
const std::array attachments{
wgpu::RenderPassColorAttachment{
.view = m_outputView,
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = {0.f, 0.f, 0.f, 0.f},
},
};
const wgpu::RenderPassDescriptor renderPassDesc{
.label = "RmlUi output copy pass",
.colorAttachmentCount = attachments.size(),
.colorAttachments = attachments.data(),
.depthStencilAttachment = &depthStencilAttachment,
};
m_pass = m_encoder.BeginRenderPass(&renderPassDesc);
m_pass.SetViewport(0.f, 0.f, static_cast<float>(m_frameSize.width), static_cast<float>(m_frameSize.height), 0.f, 1.f);
m_pass.SetScissorRect(0, 0, m_frameSize.width, m_frameSize.height);
m_pass.SetStencilReference(0);
DrawFullscreenTexture(m_layers[0].bindGroup, m_blitPipelines[static_cast<size_t>(BlitPipelineType::Replace)]);
EndActivePass();
m_layerStack.clear();
if (!m_layers.empty()) {
m_layers[0] = {};
}
m_encoder = nullptr;
m_outputView = nullptr;
}
Rml::LayerHandle WebGPURenderInterface::PushLayer() {
+6 -3
View File
@@ -7,6 +7,10 @@
#include "../gfx/clear.hpp"
#include "RmlUi/Core/RenderInterface.h"
namespace aurora::webgpu {
struct TextureWithSampler;
} // namespace aurora::webgpu
namespace aurora::rmlui {
inline constexpr uint32_t MaxHighQualityBlurRadius = 64;
@@ -92,7 +96,6 @@ class WebGPURenderInterface : public Rml::RenderInterface {
};
wgpu::CommandEncoder m_encoder;
wgpu::TextureView m_outputView;
wgpu::RenderPassEncoder m_pass;
std::array<wgpu::RenderPipeline, static_cast<size_t>(PipelineType::Count)> m_pipelines;
@@ -156,6 +159,7 @@ class WebGPURenderInterface : public Rml::RenderInterface {
void EnsureRenderTarget(RenderTarget& target, const char* label, const wgpu::Extent3D& size);
void EnsureFrameTargets(const wgpu::Extent3D& size);
wgpu::BindGroup CreateImageBindGroup(const wgpu::TextureView& view) const;
Rml::Rectanglei GetActiveScissorRegion() const;
TexCoordLimits GetPostprocessTexCoordLimits() const;
TexCoordLimits GetPostprocessTexCoordLimits(Rml::Rectanglei region) const;
@@ -205,8 +209,7 @@ public:
Rml::TextureHandle texture) override;
void ReleaseShader(Rml::CompiledShaderHandle shader) override;
void BeginFrame(const wgpu::CommandEncoder& encoder, const wgpu::TextureView& outputView, const wgpu::Extent3D& size,
const gfx::Viewport& viewport);
void BeginFrame(const wgpu::CommandEncoder& encoder, const webgpu::TextureWithSampler& target);
void EndFrame();
void SetWindowSize(const Rml::Vector2i& window_size) { m_windowSize = window_size; }
void SetRenderTargetFormat(wgpu::TextureFormat render_target_format) { m_renderTargetFormat = render_target_format; }
+38 -7
View File
@@ -1,7 +1,7 @@
#include "gpu.hpp"
#include <array>
#include <cstddef>
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
@@ -9,7 +9,6 @@
#include <aurora/aurora.h>
#include <aurora/gfx.h>
#include <magic_enum.hpp>
#include <webgpu/webgpu.h>
#include <webgpu/webgpu_cpp.h>
#include "../gfx/common.hpp"
@@ -127,6 +126,38 @@ TextureWithSampler create_render_texture(uint32_t width, uint32_t height, bool m
};
}
const TextureWithSampler& present_source() noexcept {
return g_graphicsConfig.msaaSamples > 1 ? g_frameBufferResolved : g_frameBuffer;
}
Viewport calculate_present_viewport(uint32_t surface_width, uint32_t surface_height, uint32_t content_width,
uint32_t content_height) noexcept {
if (surface_width == 0 || surface_height == 0 || content_width == 0 || content_height == 0) {
return {};
}
uint32_t viewport_width = surface_width;
uint32_t viewport_height = std::min<uint32_t>(
surface_height, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(static_cast<double>(viewport_width) *
static_cast<double>(content_height) /
static_cast<double>(content_width)))));
if (viewport_height == surface_height) {
viewport_width = std::min<uint32_t>(
surface_width, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(static_cast<double>(viewport_height) *
static_cast<double>(content_width) /
static_cast<double>(content_height)))));
}
return {
.left = static_cast<float>((surface_width - viewport_width) / 2),
.top = static_cast<float>((surface_height - viewport_height) / 2),
.width = static_cast<float>(viewport_width),
.height = static_cast<float>(viewport_height),
.znear = 0.f,
.zfar = 1.f,
};
}
static TextureWithSampler create_depth_texture(uint32_t width, uint32_t height) {
const wgpu::Extent3D size{
.width = width,
@@ -277,15 +308,15 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
g_CopyPipeline = g_device.CreateRenderPipeline(&pipelineDescriptor);
}
void create_copy_bind_group() {
wgpu::BindGroup create_copy_bind_group(const TextureWithSampler& source) {
const std::array bindGroupEntries{
wgpu::BindGroupEntry{
.binding = 0,
.sampler = g_graphicsConfig.msaaSamples > 1 ? g_frameBufferResolved.sampler : g_frameBuffer.sampler,
.sampler = source.sampler,
},
wgpu::BindGroupEntry{
.binding = 1,
.textureView = g_graphicsConfig.msaaSamples > 1 ? g_frameBufferResolved.view : g_frameBuffer.view,
.textureView = source.view,
},
};
const wgpu::BindGroupDescriptor bindGroupDescriptor{
@@ -293,7 +324,7 @@ void create_copy_bind_group() {
.entryCount = bindGroupEntries.size(),
.entries = bindGroupEntries.data(),
};
g_CopyBindGroup = g_device.CreateBindGroup(&bindGroupDescriptor);
return g_device.CreateBindGroup(&bindGroupDescriptor);
}
static wgpu::BackendType to_wgpu_backend(AuroraBackend backend) {
@@ -668,7 +699,7 @@ void resize_swapchain(uint32_t width, uint32_t height, uint32_t native_width, ui
g_frameBuffer = create_render_texture(width, height, true);
g_frameBufferResolved = create_render_texture(width, height, false);
g_depthBuffer = create_depth_texture(width, height);
create_copy_bind_group();
g_CopyBindGroup = create_copy_bind_group(present_source());
}
} // namespace aurora::webgpu
+22 -3
View File
@@ -24,6 +24,20 @@ struct TextureWithSampler {
wgpu::TextureFormat format;
wgpu::Sampler sampler;
};
struct Viewport {
float left;
float top;
float width;
float height;
float znear;
float zfar;
bool operator==(const Viewport& rhs) const {
return left == rhs.left && top == rhs.top && width == rhs.width && height == rhs.height && znear == rhs.znear &&
zfar == rhs.zfar;
}
bool operator!=(const Viewport& rhs) const { return !(*this == rhs); }
};
extern wgpu::Device g_device;
extern wgpu::Queue g_queue;
@@ -40,13 +54,18 @@ extern wgpu::Instance g_instance;
bool initialize(AuroraBackend backend);
void shutdown();
bool refresh_surface(bool recreate = true);
void resize_swapchain(uint32_t width, uint32_t height, uint32_t native_width, uint32_t native_height, bool force = false);
void resize_swapchain(uint32_t width, uint32_t height, uint32_t native_width, uint32_t native_height,
bool force = false);
TextureWithSampler create_render_texture(uint32_t width, uint32_t height, bool multisampled);
const TextureWithSampler& present_source() noexcept;
wgpu::BindGroup create_copy_bind_group(const TextureWithSampler& source);
Viewport calculate_present_viewport(uint32_t surface_width, uint32_t surface_height, uint32_t content_width,
uint32_t content_height) noexcept;
void draw_clear(const wgpu::RenderPassEncoder& pass, bool clearColor, bool clearAlpha, bool clearDepth,
const Vec4<float>& clearColorValue, float clearDepthValue);
size_t load_from_cache(void const * key, size_t keySize, void * value, size_t valueSize, void * userdata);
void store_to_cache(void const * key, size_t keySize, void const * value, size_t valueSize, void * userdata);
size_t load_from_cache(void const* key, size_t keySize, void* value, size_t valueSize, void* userdata);
void store_to_cache(void const* key, size_t keySize, void const* value, size_t valueSize, void* userdata);
void cache_shutdown();
} // namespace aurora::webgpu
+45 -3
View File
@@ -34,6 +34,7 @@ Module Log("aurora::window");
SDL_Window* g_window;
SDL_Renderer* g_renderer;
float g_frameBufferScale = 0.f;
bool g_frameBufferAspectFit = false;
AuroraWindowSize g_windowSize;
std::vector<AuroraEvent> g_events;
@@ -64,6 +65,25 @@ Vec2<int> scale_frame_buffer_to_aspect(int base_width, int base_height, float sc
};
}
Vec2<int> fit_frame_buffer_to_aspect(int width, int height, float aspect) {
if (width <= 0 || height <= 0 || aspect <= 0.f) {
return {std::max(width, 1), std::max(height, 1)};
}
const float targetAspect = static_cast<float>(width) / static_cast<float>(height);
if (targetAspect > aspect) {
return {
std::max(1, static_cast<int>(std::lround(static_cast<float>(height) * aspect))),
height,
};
}
return {
width,
std::max(1, static_cast<int>(std::lround(static_cast<float>(width) / aspect))),
};
}
void resize_swapchain() noexcept {
const auto size = get_window_size();
if (size == g_windowSize) {
@@ -380,6 +400,15 @@ AuroraWindowSize get_window_size() {
fb_w = scaledW;
fb_h = scaledH;
}
if (g_frameBufferAspectFit) {
const auto [baseW, baseH] = vi::configured_fb_size();
if (baseW > 0 && baseH > 0) {
const auto [fitW, fitH] =
fit_frame_buffer_to_aspect(fb_w, fb_h, static_cast<float>(baseW) / static_cast<float>(baseH));
fb_w = fitW;
fb_h = fitH;
}
}
const float scale = SDL_GetWindowDisplayScale(g_window);
return {
@@ -426,6 +455,13 @@ static void push_future_resize_event() {
TRY_WARN(SDL_PushEvent(&event), "Failed to push SDL event for future resize: {}", SDL_GetError());
}
void request_frame_buffer_resize() {
if (g_window != nullptr) {
// Defer so that we don't try to reconfigure the surface in the middle of game logic.
push_future_resize_event();
}
}
void set_frame_buffer_scale(float scale) {
if (scale < 0.f) {
scale = 0.f;
@@ -434,10 +470,16 @@ void set_frame_buffer_scale(float scale) {
return;
}
g_frameBufferScale = scale;
if (g_window != nullptr) {
// Defer so that we don't try to reconfigure the surface in the middle of game logic.
push_future_resize_event();
request_frame_buffer_resize();
}
void set_frame_buffer_aspect_fit(bool fit) {
if (g_frameBufferAspectFit == fit) {
return;
}
g_frameBufferAspectFit = fit;
request_frame_buffer_resize();
}
} // namespace aurora::window
+2
View File
@@ -23,5 +23,7 @@ bool get_fullscreen();
void set_window_size(uint32_t width, uint32_t height);
void set_window_position(uint32_t x, uint32_t y);
void center_window();
void request_frame_buffer_resize();
void set_frame_buffer_scale(float scale);
void set_frame_buffer_aspect_fit(bool fit);
}; // namespace aurora::window
+1
View File
@@ -248,6 +248,7 @@ bool try_bind_replacement(GXTexObj_&, GXTexMapID) noexcept { return false; }
#include "../lib/window.hpp"
namespace aurora::window {
AuroraWindowSize get_window_size() { return {640, 480, 640, 480, 640, 480, 1.0f}; }
void set_frame_buffer_aspect_fit(bool) {}
} // namespace aurora::window
// --- WebGPU C API stubs (prevent linker errors from wgpu:: destructors) ---