From af162bbd0a09bc4fef4bf54e33a4a912763e83a2 Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Sun, 24 May 2026 09:41:59 -0700 Subject: [PATCH] New Depth of Field implementation (#1773) --- include/dusk/settings.h | 14 ++- src/d/d_menu_map_common.cpp | 2 +- src/dusk/config.cpp | 1 + src/dusk/settings.cpp | 4 +- src/dusk/ui/graphics_tuner.cpp | 16 ++++ src/dusk/ui/graphics_tuner.hpp | 1 + src/dusk/ui/preset.cpp | 2 + src/dusk/ui/settings.cpp | 21 +++-- src/m_Do/m_Do_graphic.cpp | 158 +++++++++++++++++++++++++++------ 9 files changed, 183 insertions(+), 36 deletions(-) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index fb3704fd30..e842c1177a 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -15,6 +15,12 @@ enum class BloomMode : int { Dusk = 2, }; +enum class DepthOfFieldMode : int { + Off = 0, + Classic = 1, + Dusk = 2, +}; + enum class Resampler : int { Bilinear = 0, Area = 1, @@ -58,6 +64,12 @@ struct ConfigEnumRange { static constexpr auto max = BloomMode::Dusk; }; +template <> +struct ConfigEnumRange { + static constexpr auto min = DepthOfFieldMode::Off; + static constexpr auto max = DepthOfFieldMode::Dusk; +}; + template <> struct ConfigEnumRange { static constexpr auto min = Resampler::Bilinear; @@ -160,13 +172,13 @@ struct UserSettings { // Graphics ConfigVar bloomMode; ConfigVar bloomMultiplier; + ConfigVar depthOfFieldMode; ConfigVar disableWaterRefraction; ConfigVar enableTextureReplacements; ConfigVar enableFrameInterpolation; ConfigVar internalResolutionScale; ConfigVar shadowResolutionMultiplier; ConfigVar resampler; - ConfigVar enableDepthOfField; ConfigVar enableMapBackground; ConfigVar disableCutscenePillarboxing; diff --git a/src/d/d_menu_map_common.cpp b/src/d/d_menu_map_common.cpp index 6c8e0b76eb..4168673704 100644 --- a/src/d/d_menu_map_common.cpp +++ b/src/d/d_menu_map_common.cpp @@ -750,7 +750,7 @@ void dMenuMapCommon_c::debugIcon() { #if TARGET_PC static constexpr struct { - std::string stagename; + std::string_view stagename; u8 switch_no; s8 region_id; u8 save_id; diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index caa65bc0b4..c0edd84591 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -172,6 +172,7 @@ namespace dusk::config { template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; + template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 4ba40d921b..7c4e42763a 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -60,13 +60,13 @@ UserSettings g_userSettings = { // Graphics .bloomMode {"game.bloomMode", BloomMode::Dusk}, .bloomMultiplier {"game.bloomMultiplier", 1.0f}, + .depthOfFieldMode{"game.depthOfFieldMode", DepthOfFieldMode::Dusk}, .disableWaterRefraction {"game.disableWaterRefraction", false}, .enableTextureReplacements {"game.enableTextureReplacements", true}, .enableFrameInterpolation {"game.enableFrameInterpolation", FrameInterpMode::Off}, .internalResolutionScale {"game.internalResolutionScale", 0}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, .resampler {"game.resampler", Resampler::Bilinear}, - .enableDepthOfField {"game.enableDepthOfField", true}, .enableMapBackground {"game.enableMapBackground", true}, .disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false}, @@ -225,12 +225,12 @@ void registerSettings() { Register(g_userSettings.game.enableDiscordPresence); Register(g_userSettings.game.bloomMode); Register(g_userSettings.game.bloomMultiplier); + Register(g_userSettings.game.depthOfFieldMode); Register(g_userSettings.game.disableWaterRefraction); Register(g_userSettings.game.enableTextureReplacements); Register(g_userSettings.game.internalResolutionScale); Register(g_userSettings.game.resampler); Register(g_userSettings.game.shadowResolutionMultiplier); - Register(g_userSettings.game.enableDepthOfField); Register(g_userSettings.game.enableMapBackground); Register(g_userSettings.game.disableCutscenePillarboxing); Register(g_userSettings.game.enableFastIronBoots); diff --git a/src/dusk/ui/graphics_tuner.cpp b/src/dusk/ui/graphics_tuner.cpp index 0b75b27a89..8cea89e984 100644 --- a/src/dusk/ui/graphics_tuner.cpp +++ b/src/dusk/ui/graphics_tuner.cpp @@ -52,6 +52,8 @@ int get_value(GraphicsOption option) { return std::clamp( static_cast(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0, 100); + case GraphicsOption::DepthOfFieldMode: + return static_cast(getSettings().game.depthOfFieldMode.getValue()); } return 0; } @@ -85,6 +87,10 @@ void set_value(GraphicsOption option, int value) { getSettings().game.bloomMode.setValue(static_cast(std::clamp( value, static_cast(BloomMode::Off), static_cast(BloomMode::Dusk)))); break; + case GraphicsOption::DepthOfFieldMode: + getSettings().game.depthOfFieldMode.setValue(static_cast(std::clamp( + value, static_cast(DepthOfFieldMode::Off), static_cast(DepthOfFieldMode::Dusk)))); + break; case GraphicsOption::BloomMultiplier: getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f); break; @@ -214,6 +220,16 @@ Rml::String format_graphics_setting_value(GraphicsOption option, int value) { return "Dusklight"; } break; + case GraphicsOption::DepthOfFieldMode: + switch (static_cast(value)) { + case DepthOfFieldMode::Off: + return "Off"; + case DepthOfFieldMode::Classic: + return "Classic"; + case DepthOfFieldMode::Dusk: + return "Dusklight"; + } + break; case GraphicsOption::BloomMultiplier: return fmt::format("{}%", value); } diff --git a/src/dusk/ui/graphics_tuner.hpp b/src/dusk/ui/graphics_tuner.hpp index 6f8f113d13..2f5b9c5611 100644 --- a/src/dusk/ui/graphics_tuner.hpp +++ b/src/dusk/ui/graphics_tuner.hpp @@ -45,6 +45,7 @@ enum class GraphicsOption { Resampler, BloomMode, BloomMultiplier, + DepthOfFieldMode, }; Rml::String format_graphics_setting_value(GraphicsOption option, int value); diff --git a/src/dusk/ui/preset.cpp b/src/dusk/ui/preset.cpp index d1a7d1ab79..4d5950f1f4 100644 --- a/src/dusk/ui/preset.cpp +++ b/src/dusk/ui/preset.cpp @@ -14,6 +14,7 @@ void applyPresetClassic() { auto& s = getSettings(); s.video.lockAspectRatio.setValue(true); s.game.bloomMode.setValue(BloomMode::Classic); + s.game.depthOfFieldMode.setValue(DepthOfFieldMode::Classic); s.game.enableAchievementToasts.setValue(false); s.game.enableControllerToasts.setValue(false); s.game.internalResolutionScale.setValue(1); @@ -44,6 +45,7 @@ void applyPresetDusk() { s.game.enableFrameInterpolation.setValue(FrameInterpMode::Unlimited); s.game.sunsSong.setValue(true); s.game.bloomMode.setValue(BloomMode::Dusk); + s.game.depthOfFieldMode.setValue(DepthOfFieldMode::Dusk); s.game.internalResolutionScale.setValue(0); s.game.shadowResolutionMultiplier.setValue(4); s.game.enableGyroAim.setValue(true); diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index 42f205f12c..3be9ce25e2 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -370,6 +370,9 @@ const Rml::String kBloomHelpText = "a higher-quality bloom pass."; const Rml::String kBloomBrightnessHelpText = "Configure bloom intensity. Higher values make bright areas glow more strongly."; +const Rml::String kDepthOfFieldHelpText = + "Configure the post-processing depth-of-field effect. Classic uses the original depth-of-field pass;" + " Dusklight uses a higher-quality depth-of-field pass."; const Rml::String kUnlockFramerateHelpText = "
Uses inter-frame interpolation to enable higher frame rates.

May introduce minor " "visual artifacts or animation glitches."; @@ -842,7 +845,18 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) { .valueMax = 100, .defaultValue = 100, .step = 10, - }, mPrelaunch); + }, + mPrelaunch); + graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.depthOfFieldMode, + GraphicsTunerProps{ + .option = GraphicsOption::DepthOfFieldMode, + .title = "Depth of Field", + .helpText = kDepthOfFieldHelpText, + .valueMin = static_cast(DepthOfFieldMode::Off), + .valueMax = static_cast(DepthOfFieldMode::Dusk), + .defaultValue = static_cast(DepthOfFieldMode::Classic), + }, + mPrelaunch); leftPane.add_section("Rendering"); config_bool_select(leftPane, rightPane, getSettings().game.enableTextureReplacements, @@ -884,11 +898,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) { config_int_select(leftPane, rightPane, getSettings().video.maxFrameRate, "Framerate Cap", "Limit the framerate to the specified value.", 30, 540, 1, [] { return getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Capped; }); - config_bool_select(leftPane, rightPane, getSettings().game.enableDepthOfField, - { - .key = "Enable Depth of Field", - .helpText = "Render a blurring effect for out-of-focus areas in some situations. May impact performance." - }); config_bool_select(leftPane, rightPane, getSettings().game.enableMapBackground, { .key = "Enable Mini-Map Shadows", diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 65a2b5b95a..caf823b065 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -928,6 +928,103 @@ void mDoGph_drawFilterQuad(s8 param_0, s8 param_1) { GXTexCoord2s8(0, 1); GXEnd(); } + +static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, u16 dstWidth, u16 dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) { + GXSetTexCopyDst(dstWidth, dstHeight, dstFmt, FALSE); + GXCopyTex((void*)texID, false); + GXInitTexObj(pDst, (void*)texID, dstWidth, dstHeight, dstFmt, GX_CLAMP, GX_CLAMP, GX_FALSE); + GXInitTexObjLOD(pDst, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); +} + +static void drawDepth_blurTex(TGXTexObj &dst) { + u32 hw = u32(JUTVideo::getManager()->getRenderWidth()) >> 1; + u32 hh = u32(JUTVideo::getManager()->getRenderHeight()) >> 1; + + Mtx44 ortho; + C_MTXOrtho(ortho, 0.0f, hh, 0.0f, hw, 0.0f, 10.0f); + GXLoadPosMtxImm(cMtx_getIdentity(), GX_PNMTX0); + GXSetProjection(ortho, GX_ORTHOGRAPHIC); + GXSetCurrentMtx(GX_PNMTX0); + GXClearVtxDesc(); + GXSetVtxDesc(GX_VA_POS, GX_DIRECT); + GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_S8, 0); + + GXCreateFrameBuffer(hw, hh); + + auto divCopySrc = [&](int divNo) { + u32 w = u32(hw) >> divNo, h = u32(hh) >> divNo; + GXSetTexCopySrc(0, 0, w, h); + }; + + enum { MaxTexNum = 4 }; + TGXTexObj tmpTex[MaxTexNum]; + auto divCopyTex = [&](uintptr_t texNo, int divNo) -> GXTexObj* { + u32 w = u32(hw) >> divNo, h = u32(hh) >> divNo; + CopyToTexObj(&tmpTex[texNo], texNo, w, h); + return &tmpTex[texNo]; + }; + + auto divQuad = [&](int divNo) { + u32 w = u32(hw) >> divNo, h = u32(hh) >> divNo; + f32 x0 = 0.0f, y0 = 0.0f; + f32 x1 = w, y1 = h; + GXBegin(GX_QUADS, GX_VTXFMT0, 4); + GXPosition3f32(x0, y0, -5); + GXTexCoord2s8(0, 0); + GXPosition3f32(x1, y0, -5); + GXTexCoord2s8(1, 0); + GXPosition3f32(x1, y1, -5); + GXTexCoord2s8(1, 1); + GXPosition3f32(x0, y1, -5); + GXTexCoord2s8(0, 1); + GXEnd(); + }; + + u32 texMtxID = GX_TEXMTX0; + int angle = 0; + float blurScale = 0.003f; + GXSetNumTexGens(8); + GXSetNumTevStages(8); + for (int stage = 0; stage < 8; stage++) { + GXSetTexCoordGen((GXTexCoordID)stage, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); + mDoMtx_stack_c::transS( + (blurScale * cM_scos(angle)) * mDoGph_gInf_c::getInvScale(), blurScale * cM_ssin(angle), 0.0f); + GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); + texMtxID += 3; + angle += 0x2000; + + GXTevStageID tevStage = (GXTevStageID)stage; + GXSetTevOrder(tevStage, (GXTexCoordID)stage, GX_TEXMAP1, GX_COLOR_NULL); + GXSetTevColorIn(tevStage, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A1, stage == 0 ? GX_CC_ZERO : GX_CC_CPREV); + GXSetTevColorOp(tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(tevStage, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); + GXSetTevAlphaOp(tevStage, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + } + GXSetTevColor(GX_TEVREG1, {0, 0, 0, 256 / 8}); + + // assume the input tex obj is in GX_TEXMAP1 + int divNum = 3; + for (int i = 0; i < divNum; i++) { + // Apply blur filter. + divQuad(i); + + // Copy to next layer. + divCopySrc(i); + + // Set up for the next pass down. + GXTexObj* blurTex = divCopyTex(i, i + 1); + GXLoadTexObj(blurTex, GX_TEXMAP1); + } + + // upsample back to half-res buffer 0 + divQuad(0); + divCopySrc(0); + CopyToTexObj(&dst, 100, hw, hh); + + GXRestoreFrameBuffer(); +} #endif static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_2) { @@ -1081,6 +1178,21 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ } #endif +#if TARGET_PC + if (dusk::getSettings().game.depthOfFieldMode.getValue() == dusk::DepthOfFieldMode::Off) + return; + + if (!(l_tevColor0.a > -255 && sp8 == 1)) + return; + + TGXTexObj blurTex; + if (dusk::getSettings().game.depthOfFieldMode.getValue() == dusk::DepthOfFieldMode::Dusk) + { + drawDepth_blurTex(blurTex); + GXLoadTexObj(&blurTex, GX_TEXMAP1); + } +#endif + GXSetTevColorS10(GX_TEVREG0, l_tevColor0); GXSetTevSwapModeTable(GX_TEV_SWAP3, GX_CH_ALPHA, GX_CH_GREEN, GX_CH_BLUE, GX_CH_RED); GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP3); @@ -1129,37 +1241,42 @@ static void drawDepth2(view_class* param_0, view_port_class* param_1, int param_ param_1->x_orig + param_1->width, 0.0f, 10.0f); GXLoadPosMtxImm(cMtx_getIdentity(), 0); - #if DEBUG +#if DEBUG mDoMtx_stack_c::transS(g_kankyoHIO.navy.demo_focus_offset_x, g_kankyoHIO.navy.demo_focus_offset_y, 0.0f); - #else +#else mDoMtx_stack_c::transS(0.0025f, 0.0025f, 0.0f); - #endif - GXLoadTexMtxImm(mDoMtx_stack_c::get(), 0x1e, GX_MTX2x4); +#endif + GXLoadTexMtxImm(mDoMtx_stack_c::get(), GX_TEXMTX0, GX_MTX2x4); - #if DEBUG +#if DEBUG mDoMtx_stack_c::transS(-g_kankyoHIO.navy.demo_focus_offset_x, -g_kankyoHIO.navy.demo_focus_offset_y, 0.0f); - #else +#else mDoMtx_stack_c::transS(-0.0025f, -0.0025f, 0.0f); - #endif - GXLoadTexMtxImm(mDoMtx_stack_c::get(), 0x21, GX_MTX2x4); +#endif + GXLoadTexMtxImm(mDoMtx_stack_c::get(), GX_TEXMTX1, GX_MTX2x4); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_S16, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_POS_XYZ, GX_S8, 0); - GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 0x3c); - GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, 0x1e); - GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX0, 0x21); + GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0); + GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX1); GXSetNumChans(0); GXSetNumTexGens(3); GXSetNumTevStages(4); - GXSetProjection(ortho, GX_ORTHOGRAPHIC); - GXSetCurrentMtx(0); -#ifdef TARGET_PC - if (dusk::getSettings().game.enableDepthOfField) + GXSetProjection(ortho, GX_ORTHOGRAPHIC); + GXSetCurrentMtx(GX_PNMTX0); + +#if TARGET_PC + if (dusk::getSettings().game.depthOfFieldMode.getValue() == dusk::DepthOfFieldMode::Dusk) { + GXSetNumTevStages(3); + GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR_NULL); + } #endif + if (l_tevColor0.a > -255 && sp8 == 1) { GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition3s16(x_orig, y_orig_pos, -5); @@ -1334,19 +1451,8 @@ void mDoGph_gInf_c::bloom_c::remove() { } #if TARGET_PC -static void CopyToTexObj(GXTexObj* pDst, uintptr_t texID, u16 dstWidth, u16 dstHeight, GXTexFmt dstFmt = GX_TF_RGBA8) { - GXSetTexCopyDst(dstWidth, dstHeight, dstFmt, FALSE); - GXCopyTex((void*)texID, false); - GXInitTexObj(pDst, (void*)texID, dstWidth, dstHeight, dstFmt, GX_CLAMP, GX_CLAMP, GX_FALSE); - GXInitTexObjLOD(pDst, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); -} - void mDoGph_gInf_c::bloom_c::draw2() { ZoneScoped; - // if (!dusk::getSettings().game.enableBloom) { - // return; - // } - bool enabled = mEnable; if (mMonoColor.a == 0 && !enabled) return;