Skip to content

bugfix(dx8): Render the scene into an A8R8G8B8 target when the fullscreen back buffer uses the display format - #3657

Open
coconutbird wants to merge 4 commits into
TheSuperHackers:mainfrom
coconutbird:fix/fullscreen-scene-alpha-target
Open

coconutbird wants to merge 4 commits into
TheSuperHackers:mainfrom
coconutbird:fix/fullscreen-scene-alpha-target

Conversation

@coconutbird

@coconutbird coconutbird commented Nov 5, 2026 •

Copy link
Copy Markdown

Problem

#3656 uses the display format (X8R8G8B8) for the fullscreen back buffer when a device reset does not last until a frame is presented. That back buffer has no destination alpha, so on affected systems the shoreline water effect is off for the rest of the session: renderShoreLinesSorted writes the water depth into the alpha channel of the back buffer, and W3DWater blends standing water with D3DBLEND_DESTALPHA. Without it, shallow water at the shore is opaque and the edge of the water mesh shows above the surf.

Change

  • When the fullscreen back buffer uses the display format, DX8Wrapper renders the scene into an A8R8G8B8 render target texture and draws it onto the back buffer with a full-screen quad before Present(). A quad is needed because CopyRects does not convert A8R8G8B8 to X8R8G8B8.
  • With multisampling, the scene target is multisampled instead of the back buffer. The scene is rendered into a multisampled A8R8G8B8 surface with its own depth buffer, which CopyRects resolves into the texture before the quad is drawn. The back buffer only receives the finished frame, so it is not multisampled. This is decided before Reset() in Prepare_Scene_Alpha_Target, because it changes the presentation parameters.
  • The target stays the device's render target for the whole frame, so code that restores the previous render target (water reflection, texture projectors, screen filters) returns to it. It is released before Reset() and created again after it.
  • getBackBufferFormat() reports A8R8G8B8 while the target exists, so the existing destination alpha checks are unchanged.
  • Heat haze (W3DSmudge) and the profiler frame capture copy the scene in the middle of the frame, so they read the new _Get_DX8_Scene_Surface() instead of the back buffer.
  • All device states are captured in a state block before the copy and restored after it.

Nothing changes in windowed mode, or in fullscreen when the fallback from #3656 does not trigger.

Cost

Only when the fallback is active, at 2560x1440:

No MSAA 4x MSAA
Extra video memory 15 MB 44 MB
Extra GPU time per frame on an RTX 5070 0.018 ms 0.074 ms

The memory is 4 bytes per pixel without MSAA, and 12 bytes per pixel with MSAA at any sample count, because the back buffer and its depth buffer are no longer multisampled. The GPU times were measured with a standalone D3D8 test using random image data, and are probably best case because the GPU cache holds the whole frame.

Screenshots

Shoreline crop of the same frame of the default shell map in three windowed runs at 2560x1440. The X8R8G8B8 runs use a temporary local switch that is not part of this change.

Original, this change, and #3656 alone

Testing

On the system that reproduces #3049 (Windows 11 26200, RTX 5070 driver 32.0.16.1742, two 2560x1440 @ 144 Hz monitors), Zero Hour:

  • Windowed, default shell map at 2560x1440, with a temporary local switch that forces an X8R8G8B8 back buffer and the scene target: the back buffer equals the scene target in 10 of 10 captures, including after a forced reset. Compared with the original A8R8G8B8 back buffer in the matching frame, 467 pixels differ, all inside the on-screen clock. With the X8R8G8B8 back buffer and no scene target (bugfix(dx8): Use the display format for the fullscreen back buffer when a device reset does not last #3656 alone), the standing water at the shore is opaque.
  • Fullscreen without MSAA: the fallback from bugfix(dx8): Use the display format for the fullscreen back buffer when a device reset does not last #3656 triggers at startup, the scene target is created, frames are presented at the frame rate limit and the captures match the scene target. A device loss during play and returning from alt-tab both reset with the target released and created again.
  • Fullscreen with 4x MSAA: the back buffer is not multisampled, the scene target is 4x multisampled with its own depth buffer, and the captures match. The picture, including the shoreline and anti-aliased edges, looks the same as the original.

An earlier revision of this change kept the back buffer multisampled. Device loss and alt-tab with 4x MSAA were tested with that revision: both reset and frames resumed.

Not tested:

  • Generals (it builds and uses the same code).
  • Device loss and alt-tab with MSAA on this revision.
  • Screen filters, which are only reachable from demo commands.
  • Heat haze with visible smudges.
  • Systems where the fallback does not trigger (this code is not reached).

AI Disclosure

Claude Opus 5.5 wrote this PR entirely: the code, the investigation and test tooling, and this description. I directed the work.

…en a device reset does not last

In fullscreen the back buffer is promoted to D3DFMT_A8R8G8B8 while the display mode is D3DFMT_X8R8G8B8.
Because the formats differ, every IDirect3DDevice8::Reset() sets the display mode again. On systems where
a display mode change loses the device (seen on Windows 11 with multiple monitors and GPUs), the device is
lost again by its own Reset() before a single frame is presented, so the game resets forever while the
screen keeps switching modes.

When the previous fullscreen Reset() did not last until a frame was presented, Reset_Device now switches the
back buffer to the display format, which lets Reset() restore the device without a display mode change.
Destination alpha effects such as the soft water edge are then unavailable for the rest of the session.
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

Walkthrough

DX8Wrapper adds fullscreen back-buffer format handling and an optional alpha scene target. It copies the rendered scene to the back buffer before presentation. Frame capture and smudge rendering now obtain the scene surface through the new accessor.

Changes

Fullscreen scene rendering

Layer / File(s) Summary
Fullscreen reset and target setup
Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h, Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp
DX8Wrapper tracks fullscreen resets without a successful presentation. It selects a display-format back buffer in the fallback case and prepares the alpha scene target when device capabilities permit.
Scene target lifecycle and presentation
Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp
DX8Wrapper creates and releases the scene target, copies its contents to the back buffer before presentation, and clears the reset counter after a successful presentation or primary-buffer flip.
Scene surface access and consumers
Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h, Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp, Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp, Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp
The scene-surface accessor returns the alpha target when available, or the back buffer otherwise. Frame capture and smudge rendering use the accessor.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant DX8Wrapper
  participant Direct3DDevice
  participant SceneAlphaTarget
  participant BackBuffer
  DX8Wrapper->>Direct3DDevice: Reset with selected fullscreen format
  DX8Wrapper->>SceneAlphaTarget: Create target when supported
  DX8Wrapper->>BackBuffer: Copy scene before presentation
  DX8Wrapper->>Direct3DDevice: Present
Loading

Suggested labels: Rendering, Bug, Fix, Gen, ZH

Suggested reviewers: xezon, stephanmeesters

Merge Risk: Moderate · up to f1df1

On multi-adapter fullscreen setups, the display-format fallback may be wrongly rejected, leaving the device in a reset cycle. On some devices, MSAA may also be dropped unnecessarily. Fix the adapter used for the depth-format check before merging.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title identifies the main change: rendering to an A8R8G8B8 scene target when the fullscreen back buffer uses the display format.
Description check Passed The description covers the scene target, fullscreen fallback, multisampling, and related changes.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@greptile-apps

greptile-apps Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The latest change appears safe to merge.

Summary

The PR preserves shoreline transparency by rendering into an A8R8G8B8 scene target when fullscreen needs a display-format back buffer.

  • Fullscreen fallback keeps alpha while presenting through the display-format buffer.
  • Smudges and profiler captures read the surface holding the scene.

Reviews (3) · Last reviewed commit: "bugfix(dx8): Keep the back buffer format..." · Reviewed by Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Nitpick comments: 1

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp: 1

328-328: Trivial · Functional Correctness · Quick win

Test heat haze on the scene surface with MSAA fullscreen fallback.

W3DSmudgeManager::render now copies the heat haze background from DX8Wrapper::_Get_DX8_Scene_Surface() (W3DSmudge.cpp:445). With MSAA, the scene render target is multisampled. The presentation resolve copies it into a separate texture, so the smudge copy reads a different surface than before.

No automated test covers this copy. The PR also states that visible heat haze was not tested. Run the game in fullscreen fallback with MSAA enabled. Confirm that heat haze renders correctly.

Review scope

This request covers only the heat haze background copy. It does not claim that the copy fails.

Additional comments: 1

Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp-1728-1731: 1

1728-1731: Minor · Potential issue · Poor tradeoff

Unverified finding
We couldn't verify this from the code. It is shown for review, not as a verified issue.

The new render-target texture uses the exact display dimensions without accounting for texture dimension restrictions. Format support alone may not permit ordinary non-power-of-two fullscreen sizes on supported DX8 hardware.

CreateTexture fails and rendering continues directly into the non-alpha back buffer, leaving shoreline rendering incorrect despite A8R8G8B8 render-target format support.

This can occur when: Fallback activates at a resolution such as 1024x768 on a device whose render-target textures require power-of-two dimensions.

Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a8edac6c-ebd9-453e-8a6b-5ed3402f6a7e
Commits

Reviewing files that changed from the base of the PR and between 116fbcf and e470382.

Files selected for processing: 4

  • Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DProfilerFrameCapture.cpp
  • Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp
  • Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp
  • Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

…cannot be created

- Release only unbinds the multisampled depth buffer of the scene target when it is the one that is set.
- Report the multisampling the device has when the scene target cannot be created.
- Create the state block once with the scene target instead of every frame.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Minor · Resolve the MSAA scene surface before copying the heat-haze background. · W3DSmudge.cpp:327-328

Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp:327-328
Minor · Functional Correctness · Quick win

Resolve the MSAA scene surface before copying the heat-haze background.

With MSAA enabled, this call supplies a multisampled source to CopyRects before the presentation resolve. A supported runtime that rejects multisampled CopyRects leaves the background texture unchanged, so visible heat haze can sample empty or stale data. Add an in-frame resolve to a non-multisampled surface before background->Copy(...); the later presentation resolve is too late for this path.

Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 67a36c3f-57e8-4b3c-bbe8-8d155b1bfc0a
Commits

Reviewing files that changed from the base of the PR and between e470382 and f492321.

Files selected for processing: 2

  • Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp
  • Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

@Skyaero42

Copy link
Copy Markdown

This PR lacks AI disclosure

@coconutbird

Copy link
Copy Markdown
Author

This PR lacks AI disclosure

@Skyaero42 Thanks, added an AI Disclosure section to the description.

@coconutbird

Copy link
Copy Markdown
Author

@coderabbitai Replies to the findings that could not be posted inline:

  • Heat haze with MSAA (W3DSmudge.cpp:327): without this change, the back buffer is multisampled when MSAA is on, and W3DSmudgeManager::render already copies from it with CopyRects. With this change it copies from the multisampled scene surface instead, which is the same kind of copy. Copy_Scene_Alpha_Target_To_Back_Buffer resolves that surface with CopyRects too, and that is tested with 4x MSAA. Heat haze with visible smudges is still untested, as the description says.
  • Power-of-two render target textures (dx8wrapper.cpp:1728): if CreateTexture fails, Create_Scene_Alpha_Target releases the target, the scene renders into the back buffer, and the MSAA mode the device has is reported. The game then behaves as with bugfix(dx8): Use the display format for the fullscreen back buffer when a device reset does not last #3656 alone. The fallback only triggers on systems with the reset loop on current Windows, and GPUs with drivers for it support D3DPTEXTURECAPS_NONPOW2CONDITIONAL.

f1df119 also brings in the fix from #3656: if no depth buffer format matches the display format, the back buffer format is kept.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 309d0879-4731-4113-94a9-b17b8777fd17
Commits

Reviewing files that changed from the base of the PR and between f492321 and f1df119.

Files selected for processing: 1

  • Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

void DX8Wrapper::Use_Display_Format_For_Fullscreen_Back_Buffer()
{
D3DFORMAT zmode;
if (!Find_Z_Mode(DisplayFormat, DisplayFormat, &zmode))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major · Stability & Availability · Heavy lift

Supported by static analysis

Script executed:

rg -n 'Find_Z_Mode|Test_Z_Mode|D3DADAPTER_DEFAULT|CurRenderDevice' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 3254


Script executed:

git diff 116fbcfd3dcaa6c5bb46003528703fb02c24c925 f1df1196a01ee93a1fd99f01682fc1e80a73237b -- Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp
rg -n -F -e 'Use_Display_Format_For_Fullscreen_Back_Buffer' -e 'Reset_Device' -e 'Find_Z_Mode' -e 'Set_Render_Device' -- Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp
sed -n '900,1140p' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | nl -ba -v900
sed -n '1450,1700p' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | nl -ba -v1450

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 37757


Script executed:

sed -n '600,685p' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | nl -ba -v600
sed -n '790,875p' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | nl -ba -v790
sed -n '1126,1170p' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | nl -ba -v1126
sed -n '1920,2048p' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | nl -ba -v1920
rg -n -F -e 'CreateDevice(' -e 'Set_Render_Device(' -e 'Next_Render_Device(' -- Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.h

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 13586


Script executed:

sed -n '450,610p' Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp | nl -ba -v450

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 4981


Check depth-format support on the selected adapter.

If CurRenderDevice is not the primary adapter, Find_Z_Mode checks D3DADAPTER_DEFAULT. The new guard can reject the display-format fallback even when the selected adapter supports it. The device can then remain in the reset-without-present cycle.

Use CurRenderDevice for the fallback depth-format checks.

@coderabbitai

coderabbitai Bot commented Nov 6, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

Analysis chain

Script executed:

#!/bin/bash
set -u
smudge="Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp"
wrapper="Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp"
echo '--- Smudge outline ---'
ast-grep outline "$smudge" --items all --match 'render|testHardwareSupport|ReAcquireResources' --view expanded || true
echo '--- Wrapper outline ---'
ast-grep outline "$wrapper" --items all --match 'Scene_Alpha|Fullscreen_Back_Buffer|Reset' --view expanded || true
echo '--- Smudge relevant code ---'
rg -n -C 8 'W3DSmudgeManager::(render|testHardwareSupport|ReAcquireResources)|CopyRects|_Get_DX8_Scene_Surface' "$smudge"
echo '--- Wrapper relevant symbols ---'
rg -n -C 5 'Use_Display_Format_For_Fullscreen_Back_Buffer|Prepare_Scene_Alpha_Target|Create_Scene_Alpha_Target|Release_Scene_Alpha_Target|Copy_Scene_Alpha_Target_To_Back_Buffer|SceneAlphaMultiSampleType|CheckDepthStencilMatch|NONPOW2' "$wrapper"
echo '--- Wrapper scene target implementation ---'
sed -n '1510,1900p' "$wrapper"
echo '--- Relevant existing review comments ---'
gh api --paginate 'repos/TheSuperHackers/GeneralsGameCode/pulls/3657/comments?per_page=100' --jq '.[] | select(.body | test("CopyRects|power.of.two|NONPOW2|smudge"; "i")) | {path, line, body, user: .user.login}' || true
gh api --paginate 'repos/TheSuperHackers/GeneralsGameCode/issues/3657/comments?per_page=100' --jq '.[] | select(.body | test("CopyRects|power.of.two|NONPOW2|smudge"; "i")) | {body, user: .user.login}' || true

Length of output: 40402


web_search executed:

For Direct3D 8 IDirect3DDevice8::CopyRects, what restrictions apply when copying a multisampled render target to a non-multisampled surface, especially source and destination rectangles and size? For D3DPTEXTURECAPS_NONPOW2CONDITIONAL, can non-power-of-two textures be used as render targets, and what limitations apply? Use Microsoft Direct3D documentation where possible.

Length of output: 30816


Script executed:

#!/bin/bash
set -u
smudge="Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp"
wrapper="Core/Libraries/Source/WWVegas/WW3D2/dx8wrapper.cpp"
echo '--- Smudge hardware test and render copy ---'
sed -n '79,105p;204,310p;313,385p' "$smudge"
echo '--- Surface-copy implementations and callers ---'
rg -n -C 4 'Copy_Surface|CopyRects|CopyRect' "$smudge" Core/Libraries/Source/WWVegas/WW3D2
echo '--- Adapter selection ---'
rg -n -C 3 '\bCurRenderDevice\b|Find_Z_Mode\(' "$wrapper" | head -220
echo '--- Review comments, excluding other users ---'
gh api --paginate 'repos/TheSuperHackers/GeneralsGameCode/pulls/3657/comments?per_page=100' --jq '.[] | select(.user.login | test("coderabbit"; "i")) | {path, line, original_line, body}' || true
echo '--- Review bodies ---'
gh api --paginate 'repos/TheSuperHackers/GeneralsGameCode/pulls/3657/reviews?per_page=100' --jq '.[] | select((.user.login | test("coderabbit"; "i")) and (.body != null and .body != "")) | {state, body, commit_id}' || true

Length of output: 48924


Script executed:

#!/bin/bash
set -u
smudge="Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp"
echo '--- Heat-haze copy ---'
sed -n '385,470p' "$smudge"
echo '--- SurfaceClass::Copy definitions and call sites ---'
rg -n -C 5 'SurfaceClass::Copy\s*\(|background->Copy\s*\(|\bCopy\s*\(\s*SurfaceClass' Core
echo '--- Previous heat-haze source ---'
git show '116fbcfd3dcaa6c5bb46003528703fb02c24c925:Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DSmudge.cpp' | rg -n -C 5 '_Get_DX8_Back_Buffer|background->Copy|SurfaceClass \*backBuffer' || true

Length of output: 9650


Script executed:

#!/bin/bash
file="Core/Libraries/Source/WWVegas/WW3D2/surfaceclass.cpp"
ast-grep outline "$file" --items all --match 'Copy' --view expanded || true
sed -n '436,490p' "$file"

Length of output: 2709


@coconutbird Thanks for the details.

  • Heat haze: I withdraw the claim that a separate in-frame resolve is required. The previous code also copied from a multisampled back buffer. The tested presentation copy is useful evidence, but it does not test visible heat haze; the smudge copy uses an explicit rectangle. Please test visible heat haze in fullscreen fallback with 4x MSAA on this revision.
  • Texture dimensions: I withdraw the unverified finding as a confirmed defect. If CreateTexture fails, the code falls back to the back buffer and reports its actual MSAA mode. That fallback still loses shoreline transparency and, if MSAA was requested, disables MSAA after reset. The affected hardware conditions remain unverified.
  • Depth-format fallback: I confirmed that f1df119 keeps the existing back-buffer format when Find_Z_Mode fails. The separate blocking adapter finding remains: Find_Z_Mode checks D3DADAPTER_DEFAULT, not CurRenderDevice. Please use the selected adapter for this check before merging, and verify the fallback on a non-default adapter.

You are interacting with an AI system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants