diff --git a/extern/aurora b/extern/aurora index 1f84646de4..cf3ffc9886 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 1f84646de4c35483e580b07ab5c537c681a55e5e +Subproject commit cf3ffc98865ab209b02cb3eefe778a3f057c6db4 diff --git a/libs/JSystem/src/JUtility/JUTVideo.cpp b/libs/JSystem/src/JUtility/JUTVideo.cpp index b9a51ab6f3..663c357e17 100644 --- a/libs/JSystem/src/JUtility/JUTVideo.cpp +++ b/libs/JSystem/src/JUtility/JUTVideo.cpp @@ -70,6 +70,11 @@ void JUTVideo::preRetraceProc(u32 retrace_count) { OSTick tick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); sVideoInterval = tick - sVideoLastTick; +#if TARGET_PC + if (sVideoInterval <= 0) { + sVideoInterval = 1; + } +#endif sVideoLastTick = tick; JUTXfb* xfb = JUTXfb::getManager(); diff --git a/mods/ao_mod/src/mod.cpp b/mods/ao_mod/src/mod.cpp index fb36ddb40c..8a96ae3c95 100644 --- a/mods/ao_mod/src/mod.cpp +++ b/mods/ao_mod/src/mod.cpp @@ -53,6 +53,7 @@ ResourceBuffer g_denoiseSource = RESOURCE_BUFFER_INIT; ResourceBuffer g_compositeSource = RESOURCE_BUFFER_INIT; GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT; +GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT; WGPUComputePipeline g_preprocessPipeline = nullptr; WGPUComputePipeline g_mip4Pipeline = nullptr; WGPUComputePipeline g_gtaoPipeline = nullptr; @@ -226,19 +227,17 @@ bool build_composite_pipeline( .dstFactor = WGPUBlendFactor_One, }, }; - WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT; - colorTarget.format = g_deviceInfo.color_format; - if (blend) { - colorTarget.blend = &blendState; - } + WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS]; + const uint32_t colorTargetCount = gfx_init_color_target_states( + &g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All); WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT; fragment.module = module; fragment.entryPoint = {"fs_main", WGPU_STRLEN}; - fragment.targetCount = 1; - fragment.targets = &colorTarget; + fragment.targetCount = colorTargetCount; + fragment.targets = colorTargets; // Depth state must match the EFB pass despite never touching depth. WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT; - depthStencil.format = g_deviceInfo.depth_format; + depthStencil.format = g_sceneTargetLayout.depth_stencil_format; depthStencil.depthWriteEnabled = WGPUOptionalBool_False; depthStencil.depthCompare = WGPUCompareFunction_Always; @@ -248,7 +247,7 @@ bool build_composite_pipeline( pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN}; pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipelineDesc.depthStencil = &depthStencil; - pipelineDesc.multisample.count = g_deviceInfo.sample_count; + pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count; pipelineDesc.fragment = &fragment; outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc); wgpuShaderModuleRelease(module); @@ -504,7 +503,7 @@ void on_compute( // Render worker thread: composite the AO over the scene (or show it, in debug view). void on_draw( ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) { - if (payloadSize != sizeof(CompositePayload)) { + if (payloadSize != sizeof(CompositePayload) || ctx->layout.key != g_sceneTargetLayout.key) { return; } CompositePayload data; @@ -816,6 +815,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) { if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) { return mods::set_error(error, MOD_ERROR, "failed to query device info"); } + if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) { + return mods::set_error(error, MOD_ERROR, "failed to query scene target layout"); + } if (!build_compute_pipeline("AO preprocess depth", g_preprocessSource, "preprocess_depth", g_preprocessPipeline, g_preprocessLayout) || !build_compute_pipeline("AO downsample mip4", g_preprocessSource, "downsample_mip4", diff --git a/mods/shadow_mod/src/mod.cpp b/mods/shadow_mod/src/mod.cpp index 36f33de41d..fa4056040b 100644 --- a/mods/shadow_mod/src/mod.cpp +++ b/mods/shadow_mod/src/mod.cpp @@ -20,12 +20,12 @@ #include "dolphin/gx/GXPixel.h" #include "dolphin/gx/GXTransform.h" #include "m_Do/m_Do_mtx.h" -#include "mods/svc/hook.hpp" #include "mods/service.hpp" #include "mods/svc/camera.h" #include "mods/svc/config.h" #include "mods/svc/gfx.h" #include "mods/svc/hook.h" +#include "mods/svc/hook.hpp" #include "mods/svc/log.h" #include "mods/svc/resource.h" #include "mods/svc/ui.h" @@ -69,6 +69,7 @@ GfxStageHookHandle g_frameBeforeHudHook = 0; UiWindowHandle g_controlsWindow = 0; ResourceBuffer g_shaderSource = RESOURCE_BUFFER_INIT; GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT; +GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT; WGPURenderPipeline g_compositePipeline = nullptr; // multiply blend WGPURenderPipeline g_compositeDebugPipeline = nullptr; // no blend (debug views) WGPUBindGroupLayout g_compositeLayout = nullptr; @@ -399,18 +400,16 @@ bool build_composite_pipeline( .srcFactor = WGPUBlendFactor_Zero, .dstFactor = WGPUBlendFactor_One}, }; - WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT; - colorTarget.format = g_deviceInfo.color_format; - if (blend) { - colorTarget.blend = &blendState; - } + WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS]; + const uint32_t colorTargetCount = gfx_init_color_target_states( + &g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All); WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT; fragment.module = module; fragment.entryPoint = {"fs_main", WGPU_STRLEN}; - fragment.targetCount = 1; - fragment.targets = &colorTarget; + fragment.targetCount = colorTargetCount; + fragment.targets = colorTargets; WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT; - depthStencil.format = g_deviceInfo.depth_format; + depthStencil.format = g_sceneTargetLayout.depth_stencil_format; depthStencil.depthWriteEnabled = WGPUOptionalBool_False; depthStencil.depthCompare = WGPUCompareFunction_Always; @@ -420,7 +419,7 @@ bool build_composite_pipeline( pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN}; pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipelineDesc.depthStencil = &depthStencil; - pipelineDesc.multisample.count = g_deviceInfo.sample_count; + pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count; pipelineDesc.fragment = &fragment; outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc); wgpuShaderModuleRelease(module); @@ -518,7 +517,7 @@ WGPUBindGroup create_composite_bind_group(WGPUDevice device, WGPUBindGroupLayout // Render worker thread: fullscreen deferred-shadow composite. void on_draw( ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) { - if (payloadSize != sizeof(DrawPayload)) { + if (payloadSize != sizeof(DrawPayload) || ctx->layout.key != g_sceneTargetLayout.key) { return; } DrawPayload data; @@ -1243,6 +1242,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) { if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) { return mods::set_error(error, MOD_ERROR, "failed to query device info"); } + if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) { + return mods::set_error(error, MOD_ERROR, "failed to query scene target layout"); + } if (!build_composite_pipeline(true, g_compositePipeline, g_compositeLayout) || !build_composite_pipeline(false, g_compositeDebugPipeline, g_compositeDebugLayout)) { diff --git a/mods/window_demo/src/mod.cpp b/mods/window_demo/src/mod.cpp index fb3cc31809..00d6f1240b 100644 --- a/mods/window_demo/src/mod.cpp +++ b/mods/window_demo/src/mod.cpp @@ -189,12 +189,6 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) { 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; } diff --git a/sdk/include/mods/svc/gfx.h b/sdk/include/mods/svc/gfx.h index 5041c611e3..0eee026984 100644 --- a/sdk/include/mods/svc/gfx.h +++ b/sdk/include/mods/svc/gfx.h @@ -33,10 +33,68 @@ #define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx" #define GFX_SERVICE_MAJOR 1u -#define GFX_SERVICE_MINOR 1u +#define GFX_SERVICE_MINOR 2u /* Maximum size for push_draw payload */ #define GFX_INLINE_DRAW_PAYLOAD_SIZE 128u +#define GFX_MAX_COLOR_ATTACHMENTS 8u +#define GFX_SCENE_COLOR_ATTACHMENT_INDEX 0u + +typedef enum GfxAttachmentSemantic { + GFX_ATTACHMENT_SCENE_COLOR, + GFX_ATTACHMENT_NORMAL, + GFX_ATTACHMENT_AUXILIARY, +} GfxAttachmentSemantic; + +typedef struct GfxColorAttachmentLayout { + GfxAttachmentSemantic semantic; + WGPUTextureFormat format; + uint32_t width; + uint32_t height; +} GfxColorAttachmentLayout; + +typedef struct GfxRenderTargetLayout { + uint32_t struct_size; + uint64_t key; + /* At least one; scene color is at GFX_SCENE_COLOR_ATTACHMENT_INDEX. */ + uint32_t color_attachment_count; + GfxColorAttachmentLayout color_attachments[GFX_MAX_COLOR_ATTACHMENTS]; + WGPUTextureFormat depth_stencil_format; + uint32_t sample_count; +} GfxRenderTargetLayout; + +#define GFX_RENDER_TARGET_LAYOUT_INIT \ + {sizeof(GfxRenderTargetLayout), 0u, 0u, \ + {{GFX_ATTACHMENT_AUXILIARY, WGPUTextureFormat_Undefined}}, WGPUTextureFormat_Undefined, \ + 1u} + +/* + * Initializes pipeline color targets for a render-target layout. Only scene color is writable; + * callers that write another semantic should override that target afterward. + */ +static uint32_t gfx_init_color_target_states(const GfxRenderTargetLayout* layout, + WGPUColorTargetState targets[GFX_MAX_COLOR_ATTACHMENTS], const WGPUBlendState* scene_blend, + WGPUColorWriteMask scene_write_mask) { + if (layout == NULL || targets == NULL) { + return 0; + } + const uint32_t count = layout->color_attachment_count < GFX_MAX_COLOR_ATTACHMENTS ? + layout->color_attachment_count : + GFX_MAX_COLOR_ATTACHMENTS; + for (uint32_t i = 0; i < GFX_MAX_COLOR_ATTACHMENTS; ++i) { + WGPUColorTargetState target = WGPU_COLOR_TARGET_STATE_INIT; + if (i < count) { + target.format = layout->color_attachments[i].format; + target.writeMask = WGPUColorWriteMask_None; + } + targets[i] = target; + } + if (count != 0) { + targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].blend = scene_blend; + targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].writeMask = scene_write_mask; + } + return count; +} /* 0 is never a valid handle. */ typedef uint64_t GfxDrawTypeHandle; @@ -51,8 +109,8 @@ typedef struct GfxRange { } GfxRange; /* - * Device and scene pass configuration. Valid from mod_initialize onward and stable for the - * session. Offscreen passes from create_pass are always single-sample. + * Device and legacy primary scene-pass configuration. Use get_scene_target_layout when creating + * scene pipelines. Offscreen passes from create_pass are always single-sample. */ typedef struct GfxDeviceInfo { uint32_t struct_size; @@ -83,12 +141,18 @@ typedef struct GfxDrawContext { WGPUBuffer index_buffer; WGPUBuffer uniform_buffer; WGPUBuffer storage_buffer; + /* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].format */ WGPUTextureFormat color_format; + /* deprecated: use layout.depth_stencil_format */ WGPUTextureFormat depth_format; + /* deprecated: use layout.sample_count */ uint32_t sample_count; + /* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].width */ uint32_t target_width; + /* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].height */ uint32_t target_height; bool uses_reversed_z; + GfxRenderTargetLayout layout; /* added in GfxService 1.2 */ } GfxDrawContext; typedef void (*GfxDrawFn)(ModContext* ctx, const GfxDrawContext* draw_ctx, const void* payload, @@ -269,6 +333,10 @@ typedef struct GfxService { */ ModResult (*push_present)( ModContext* ctx, GfxPresentTargetHandle handle, const void* payload, size_t payload_size); + + /* Minor version 2 */ + + ModResult (*get_scene_target_layout)(ModContext* ctx, GfxRenderTargetLayout* out_layout); } GfxService; MOD_DECLARE_SERVICE(GfxService, svc_gfx, GFX_SERVICE_ID, GFX_SERVICE_MAJOR, GFX_SERVICE_MINOR); diff --git a/src/dusk/mods/loader/loader.cpp b/src/dusk/mods/loader/loader.cpp index 6c3a01bd7a..83c7660410 100644 --- a/src/dusk/mods/loader/loader.cpp +++ b/src/dusk/mods/loader/loader.cpp @@ -899,6 +899,7 @@ bool ModLoader::activate_mod(LoadedMod& mod) { } void ModLoader::deactivate_mod(LoadedMod& mod) { + svc::modules_mod_deactivating(mod); if (mod.initialized && mod.native && mod.native->fn_shutdown) { log::write(mod.metadata.id, LOG_LEVEL_TRACE, "calling mod_shutdown"); try { diff --git a/src/dusk/mods/svc/gfx.cpp b/src/dusk/mods/svc/gfx.cpp index 900640d4fb..098894007a 100644 --- a/src/dusk/mods/svc/gfx.cpp +++ b/src/dusk/mods/svc/gfx.cpp @@ -9,8 +9,10 @@ #include #include +#include #include +#include #include #include #include @@ -126,23 +128,34 @@ GfxSlot* resolve_owned_slot_locked(LoadedMod& mod, uint64_t handle, GfxSlotKind return &entry->value; } -void collect_mod_types_locked(LoadedMod& owner, std::vector& drawIds, +void take_mod_types_locked(LoadedMod& owner, std::vector& drawIds, std::vector& taskIds) { - s_slots.for_each([&](uint64_t, const auto& entry) { + std::vector drawHandles; + std::vector taskHandles; + s_slots.for_each([&](uint64_t handle, const auto& entry) { if (entry.owner != &owner) { return; } const auto& slot = entry.value; if (slot.kind == GfxSlotKind::DrawType && slot.auroraDrawId != aurora::gfx::InvalidDrawType) { - drawIds.push_back(slot.auroraDrawId); + drawHandles.push_back(handle); } else if ((slot.kind == GfxSlotKind::ComputeType || slot.kind == GfxSlotKind::PresentTarget) && slot.auroraTaskId != aurora::gfx::InvalidEncoderTask) { - taskIds.push_back(slot.auroraTaskId); + taskHandles.push_back(handle); } }); + for (const auto handle : drawHandles) { + auto* entry = s_slots.find(handle); + drawIds.push_back(std::exchange(entry->value.auroraDrawId, aurora::gfx::InvalidDrawType)); + } + for (const auto handle : taskHandles) { + auto* entry = s_slots.find(handle); + taskIds.push_back( + std::exchange(entry->value.auroraTaskId, aurora::gfx::InvalidEncoderTask)); + } } void unregister_aurora_types(const std::vector& drawIds, @@ -155,6 +168,49 @@ void unregister_aurora_types(const std::vector& drawIds } } +void gfx_mod_deactivating(LoadedMod& mod) { + std::vector drawIds; + std::vector taskIds; + { + std::lock_guard lock{s_mutex}; + take_mod_types_locked(mod, drawIds, taskIds); + } + unregister_aurora_types(drawIds, taskIds); + if (!drawIds.empty() || !taskIds.empty()) { + aurora::gfx::synchronize(); + } +} + +GfxAttachmentSemantic gfx_attachment_semantic(aurora::gfx::ColorAttachmentSemantic semantic) { + switch (semantic) { + case aurora::gfx::ColorAttachmentSemantic::SceneColor: + return GFX_ATTACHMENT_SCENE_COLOR; + case aurora::gfx::ColorAttachmentSemantic::Normal: + return GFX_ATTACHMENT_NORMAL; + case aurora::gfx::ColorAttachmentSemantic::Auxiliary: + return GFX_ATTACHMENT_AUXILIARY; + } + return GFX_ATTACHMENT_AUXILIARY; +} + +GfxRenderTargetLayout gfx_render_target_layout(const aurora::gfx::RenderTargetLayout& layout) { + GfxRenderTargetLayout result = GFX_RENDER_TARGET_LAYOUT_INIT; + result.key = layout.key; + result.color_attachment_count = + std::min(layout.colorAttachmentCount, GFX_MAX_COLOR_ATTACHMENTS); + for (uint32_t i = 0; i < result.color_attachment_count; ++i) { + result.color_attachments[i] = { + .semantic = gfx_attachment_semantic(layout.colorAttachments[i].semantic), + .format = static_cast(layout.colorAttachments[i].format), + .width = layout.colorAttachments[i].width, + .height = layout.colorAttachments[i].height, + }; + } + result.depth_stencil_format = static_cast(layout.depthStencilFormat); + result.sample_count = layout.sampleCount; + return result; +} + void draw_trampoline(const aurora::gfx::DrawContext& ctx, const wgpu::RenderPassEncoder& pass, const void* payload, size_t payloadSize, void* userdata) { const auto handle = static_cast(reinterpret_cast(userdata)); @@ -184,12 +240,12 @@ void draw_trampoline(const aurora::gfx::DrawContext& ctx, const wgpu::RenderPass .index_buffer = ctx.indexBuffer.Get(), .uniform_buffer = ctx.uniformBuffer.Get(), .storage_buffer = ctx.storageBuffer.Get(), - .color_format = static_cast(ctx.colorFormat), - .depth_format = static_cast(ctx.depthFormat), - .sample_count = ctx.sampleCount, - .target_width = ctx.targetWidth, - .target_height = ctx.targetHeight, + .color_format = static_cast( + ctx.layout.colorAttachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].format), + .depth_format = static_cast(ctx.layout.depthStencilFormat), + .sample_count = ctx.layout.sampleCount, .uses_reversed_z = aurora::gfx::uses_reversed_z(), + .layout = gfx_render_target_layout(ctx.layout), }; std::string failure; @@ -778,8 +834,10 @@ ModResult gfx_unregister_present_target(LoadedMod& mod, uint64_t handle) { auroraId = slot->auroraTaskId; } - aurora::gfx::unregister_encoder_task_type(auroraId); - aurora::gfx::synchronize(); + if (auroraId != aurora::gfx::InvalidEncoderTask) { + aurora::gfx::unregister_encoder_task_type(auroraId); + aurora::gfx::synchronize(); + } std::optional removed; { @@ -903,6 +961,7 @@ void gfx_run_stage( .game_viewport = gameViewport, }; + AuroraGXSync(); for (const auto& entry : entries) { { std::lock_guard lock{s_mutex}; @@ -924,6 +983,7 @@ void gfx_run_stage( fail_mod(*entry.owner, MOD_ERROR, "unknown exception in gfx stage callback"); } + AuroraGXSync(); if (aurora::gfx::is_offscreen() != wasOffscreen) { aurora::gfx::ResolvedTargets discarded; aurora::gfx::resolve_pass( @@ -935,17 +995,8 @@ void gfx_run_stage( } } -void gfx_remove_mod(LoadedMod& mod) { - std::vector drawIds; - std::vector taskIds; - { - std::lock_guard lock{s_mutex}; - collect_mod_types_locked(mod, drawIds, taskIds); - } - unregister_aurora_types(drawIds, taskIds); - if (!drawIds.empty() || !taskIds.empty()) { - aurora::gfx::synchronize(); - } +void gfx_mod_detached(LoadedMod& mod) { + gfx_mod_deactivating(mod); std::vector entries; { @@ -966,7 +1017,7 @@ void gfx_remove_mod(LoadedMod& mod) { } } -void gfx_drain_worker_failures() { +void gfx_frame_begin() { std::vector failures; { std::lock_guard lock{s_mutex}; @@ -979,7 +1030,7 @@ void gfx_drain_worker_failures() { for (const auto& failure : failures) { for (auto& mod : ModLoader::instance().mods()) { if (mod.metadata.id == failure.modId && mod.active) { - gfx_remove_mod(mod); + gfx_mod_detached(mod); fail_mod(mod, MOD_ERROR, failure.message); break; } @@ -1030,6 +1081,19 @@ ModResult gfx_get_device_info(ModContext* context, GfxDeviceInfo* outInfo) { return MOD_OK; } +ModResult gfx_get_scene_target_layout(ModContext* context, GfxRenderTargetLayout* outLayout) { + if (outLayout == nullptr || outLayout->struct_size < sizeof(GfxRenderTargetLayout) || + mod_from_context(context) == nullptr) + { + return MOD_INVALID_ARGUMENT; + } + + const uint32_t structSize = outLayout->struct_size; + *outLayout = gfx_render_target_layout(aurora::gfx::scene_render_target_layout()); + outLayout->struct_size = structSize; + return MOD_OK; +} + void* gfx_get_proc_address(ModContext* context, const char* name) { if (mod_from_context(context) == nullptr || name == nullptr) { return nullptr; @@ -1327,6 +1391,7 @@ constexpr GfxService s_gfxService{ .resize_present_target = gfx_resize_present_target_impl, .unregister_present_target = gfx_unregister_present_target_impl, .push_present = gfx_push_present_impl, + .get_scene_target_layout = gfx_get_scene_target_layout, }; } // namespace @@ -1336,8 +1401,9 @@ constinit const ServiceModule g_gfxModule{ .majorVersion = GFX_SERVICE_MAJOR, .minorVersion = GFX_SERVICE_MINOR, .service = &s_gfxService, - .modDetached = gfx_remove_mod, - .frameBegin = gfx_drain_worker_failures, + .modDeactivating = gfx_mod_deactivating, + .modDetached = gfx_mod_detached, + .frameBegin = gfx_frame_begin, }; } // namespace dusk::mods::svc diff --git a/src/dusk/mods/svc/registry.cpp b/src/dusk/mods/svc/registry.cpp index 1c8368720f..051d7ad977 100644 --- a/src/dusk/mods/svc/registry.cpp +++ b/src/dusk/mods/svc/registry.cpp @@ -150,6 +150,14 @@ ModResult register_module(const ServiceModule& module) { return MOD_OK; } +void modules_mod_deactivating(LoadedMod& mod) { + for (const auto* module : s_modules | std::views::reverse) { + if (module->modDeactivating != nullptr) { + module->modDeactivating(mod); + } + } +} + void modules_mod_detached(LoadedMod& mod) { for (const auto* module : s_modules | std::views::reverse) { if (module->modDetached != nullptr) { diff --git a/src/dusk/mods/svc/registry.hpp b/src/dusk/mods/svc/registry.hpp index d3df44ccc3..c88ef5509c 100644 --- a/src/dusk/mods/svc/registry.hpp +++ b/src/dusk/mods/svc/registry.hpp @@ -19,7 +19,7 @@ struct ServiceRecord { }; // A host service and its lifecycle hooks. Every hook is optional. Frame and lifecycle hooks run in -// registration order, modDetached in reverse registration order. +// registration order, teardown hooks in reverse registration order. struct ServiceModule { const char* id = nullptr; uint16_t majorVersion = 0; @@ -28,6 +28,9 @@ struct ServiceModule { // One-time setup, at registration (ModLoader::init_services). void (*initialize)() = nullptr; + // A mod is beginning deactivation: stop callbacks that may execute concurrently. Service state + // remains registered so mod_shutdown may release it normally. + void (*modDeactivating)(LoadedMod& mod) = nullptr; // A mod is going away (deactivation or failed activation): drop all state held for it. // Runs after the mod's mod_shutdown and before its library unloads, so pointers into // the mod are still valid but must not be called. @@ -55,6 +58,7 @@ const ServiceRecord* find_service( const ServiceRecord* find_service_record(const char* serviceId, uint16_t majorVersion); ModResult register_module(const ServiceModule& module); +void modules_mod_deactivating(LoadedMod& mod); void modules_mod_detached(LoadedMod& mod); void modules_lifecycle_applied(); void modules_frame_begin();