mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-26 06:52:40 -04:00
Mods: WindowService, log wrappers, external rendering (#2251)
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
#include "logging.hpp"
|
||||
|
||||
#include "mods/service.hpp"
|
||||
#include "mods/svc/gfx.h"
|
||||
#include "mods/svc/log.hpp"
|
||||
#include "mods/svc/ui.h"
|
||||
#include "mods/svc/window.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
DEFINE_MOD();
|
||||
IMPORT_SERVICE(LogService, svc_log);
|
||||
IMPORT_SERVICE(UiService, svc_ui);
|
||||
IMPORT_SERVICE(WindowService, svc_window);
|
||||
IMPORT_SERVICE(GfxService, svc_gfx);
|
||||
|
||||
namespace {
|
||||
|
||||
WindowHandle g_window = 0;
|
||||
GfxPresentTargetHandle g_presentTarget = 0;
|
||||
GfxStageHookHandle g_stageHook = 0;
|
||||
uint32_t g_frame = 0;
|
||||
bool g_recreatePresentTarget = false;
|
||||
|
||||
struct ClearPayload {
|
||||
float red;
|
||||
float green;
|
||||
float blue;
|
||||
float alpha;
|
||||
};
|
||||
static_assert(sizeof(ClearPayload) <= GFX_INLINE_DRAW_PAYLOAD_SIZE);
|
||||
|
||||
ModResult close_window() {
|
||||
g_recreatePresentTarget = false;
|
||||
if (g_presentTarget != 0) {
|
||||
const auto result = svc_gfx->unregister_present_target(mod_ctx, g_presentTarget);
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
g_presentTarget = 0;
|
||||
}
|
||||
if (g_window != 0) {
|
||||
const auto result = svc_window->destroy_window(mod_ctx, g_window);
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
g_window = 0;
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void on_window_event(ModContext*, WindowHandle, const WindowEvent* event, void*) {
|
||||
window_demo::log_window_event(event);
|
||||
|
||||
if (event->type == WINDOW_EVENT_CLOSE_REQUESTED) {
|
||||
if (close_window() != MOD_OK) {
|
||||
mods::log::error("failed to close auxiliary window");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render worker thread: record a clear of the acquired auxiliary surface texture.
|
||||
void on_present(
|
||||
ModContext*, const GfxPresentContext* ctx, const void* payload, size_t payloadSize, void*) {
|
||||
if (payloadSize != sizeof(ClearPayload)) {
|
||||
return;
|
||||
}
|
||||
ClearPayload color;
|
||||
std::memcpy(&color, payload, sizeof(color));
|
||||
|
||||
WGPURenderPassColorAttachment colorAttachment = WGPU_RENDER_PASS_COLOR_ATTACHMENT_INIT;
|
||||
colorAttachment.view = ctx->target_view;
|
||||
colorAttachment.loadOp = WGPULoadOp_Clear;
|
||||
colorAttachment.storeOp = WGPUStoreOp_Store;
|
||||
colorAttachment.clearValue = WGPUColor{
|
||||
color.red,
|
||||
color.green,
|
||||
color.blue,
|
||||
color.alpha,
|
||||
};
|
||||
|
||||
WGPURenderPassDescriptor passDesc = WGPU_RENDER_PASS_DESCRIPTOR_INIT;
|
||||
passDesc.label = {"Auxiliary window clear", WGPU_STRLEN};
|
||||
passDesc.colorAttachmentCount = 1;
|
||||
passDesc.colorAttachments = &colorAttachment;
|
||||
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(ctx->encoder, &passDesc);
|
||||
wgpuRenderPassEncoderEnd(pass);
|
||||
wgpuRenderPassEncoderRelease(pass);
|
||||
}
|
||||
|
||||
ModResult register_present_target() {
|
||||
if (g_window == 0 || g_presentTarget != 0) {
|
||||
return MOD_CONFLICT;
|
||||
}
|
||||
GfxPresentTargetDesc presentDesc = GFX_PRESENT_TARGET_DESC_INIT;
|
||||
presentDesc.label = "Auxiliary window surface";
|
||||
presentDesc.render = on_present;
|
||||
presentDesc.preferred_alpha_mode =
|
||||
WGPUCompositeAlphaMode_Premultiplied; // For transparent window
|
||||
return svc_gfx->register_window_present_target(
|
||||
mod_ctx, g_window, &presentDesc, &g_presentTarget);
|
||||
}
|
||||
|
||||
ModResult open_window() {
|
||||
if (g_window != 0) {
|
||||
return MOD_CONFLICT;
|
||||
}
|
||||
|
||||
WindowDesc windowDesc = WINDOW_DESC_INIT;
|
||||
windowDesc.title = "Mod window";
|
||||
windowDesc.width = 640;
|
||||
windowDesc.height = 480;
|
||||
windowDesc.on_event = on_window_event;
|
||||
windowDesc.flags |= WINDOW_FLAG_TRANSPARENT; // For transparent window
|
||||
auto result = svc_window->create_window(mod_ctx, &windowDesc, &g_window);
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = register_present_target();
|
||||
if (result != MOD_OK) {
|
||||
close_window();
|
||||
return result;
|
||||
}
|
||||
|
||||
result = svc_window->show_window(mod_ctx, g_window);
|
||||
if (result != MOD_OK) {
|
||||
close_window();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void on_frame_after_hud(ModContext*, const GfxStageContext*, void*) {
|
||||
if (g_presentTarget == 0) {
|
||||
return;
|
||||
}
|
||||
const float phase = static_cast<float>(g_frame++) * 0.015f;
|
||||
const ClearPayload color{
|
||||
.red = 0.08f + 0.06f * (std::sin(phase) + 1.0f),
|
||||
.green = 0.10f + 0.06f * (std::sin(phase + 2.1f) + 1.0f),
|
||||
.blue = 0.14f + 0.08f * (std::sin(phase + 4.2f) + 1.0f),
|
||||
.alpha = 0.5f,
|
||||
};
|
||||
if (svc_gfx->push_present(mod_ctx, g_presentTarget, &color, sizeof(color)) == MOD_ERROR) {
|
||||
g_recreatePresentTarget = true;
|
||||
}
|
||||
}
|
||||
|
||||
void on_toggle_window(ModContext*, void*) {
|
||||
if (g_window != 0) {
|
||||
if (close_window() != MOD_OK) {
|
||||
mods::log::error("failed to close auxiliary window");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (open_window() != MOD_OK) {
|
||||
mods::log::error("failed to open auxiliary window");
|
||||
}
|
||||
}
|
||||
|
||||
ModResult build_panel(ModContext*, UiElementHandle panel, void*, ModError*) {
|
||||
UiControlDesc control = UI_CONTROL_DESC_INIT;
|
||||
control.kind = UI_CONTROL_BUTTON;
|
||||
control.label = "Open / Close Window";
|
||||
control.on_pressed = on_toggle_window;
|
||||
return svc_ui->pane_add_control(mod_ctx, panel, &control, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
MOD_EXPORT ModResult mod_initialize(ModError* error) {
|
||||
GfxStageHookDesc stageDesc = GFX_STAGE_HOOK_DESC_INIT;
|
||||
stageDesc.callback = on_frame_after_hud;
|
||||
if (svc_gfx->register_stage_hook(
|
||||
mod_ctx, GFX_STAGE_FRAME_AFTER_HUD, &stageDesc, &g_stageHook) != MOD_OK)
|
||||
{
|
||||
return mods::set_error(error, MOD_ERROR, "failed to register presentation hook");
|
||||
}
|
||||
|
||||
UiModsPanelDesc panelDesc = UI_MODS_PANEL_DESC_INIT;
|
||||
panelDesc.build = build_panel;
|
||||
if (svc_ui->register_mods_panel(mod_ctx, &panelDesc) != MOD_OK) {
|
||||
svc_gfx->unregister_stage_hook(mod_ctx, g_stageHook);
|
||||
g_stageHook = 0;
|
||||
return mods::set_error(error, MOD_ERROR, "failed to register mod panel");
|
||||
}
|
||||
|
||||
if (open_window() != MOD_OK) {
|
||||
svc_gfx->unregister_stage_hook(mod_ctx, g_stageHook);
|
||||
g_stageHook = 0;
|
||||
return mods::set_error(error, MOD_ERROR, "failed to open auxiliary window");
|
||||
}
|
||||
|
||||
mods::log::info("auxiliary WebGPU window ready");
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_update(ModError* error) {
|
||||
if (!g_recreatePresentTarget || g_window == 0) {
|
||||
return MOD_OK;
|
||||
}
|
||||
g_recreatePresentTarget = false;
|
||||
if (g_presentTarget != 0) {
|
||||
const auto result = svc_gfx->unregister_present_target(mod_ctx, g_presentTarget);
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(error, result, "failed to unregister lost present target");
|
||||
}
|
||||
g_presentTarget = 0;
|
||||
}
|
||||
const auto result = register_present_target();
|
||||
if (result != MOD_OK) {
|
||||
return mods::set_error(error, result, "failed to recreate present target");
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_shutdown(ModError*) {
|
||||
if (g_stageHook != 0) {
|
||||
svc_gfx->unregister_stage_hook(mod_ctx, g_stageHook);
|
||||
g_stageHook = 0;
|
||||
}
|
||||
close_window();
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user