Skip to content

Commit

Permalink
Overhaul capture GUI + interactability
Browse files Browse the repository at this point in the history
- Move ImGUI ownership to CommonObjects, so it is more easily accessible by dxvk base types
- Added Capture section to GUI
  - Under `Enhancements` tab (for now)
  - Pops up automatically when capture initiated by hotkey, unless turned off by option
  - Able to adjust capture options at runtime
- Added Capture progress bar
- Fixed problem with golden capture case sensitivity
  • Loading branch information
nv-nfreybler committed Sep 13, 2023
2 parents 56352d7 + fb41d8c commit a320d8e
Show file tree
Hide file tree
Showing 25 changed files with 881 additions and 316 deletions.
12 changes: 8 additions & 4 deletions RtxOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,17 @@ Tables below enumerate all the options and their defaults set by RTX Remix. Note
|rtx.cameraAnimationMode|int|3|Free camera's animation mode\.|
|rtx.cameraShakePeriod|int|20|Period of the free camera's animation\.|
|rtx.captureDebugImage|bool|False||
|rtx.captureFramesPerSecond|int|24||
|rtx.captureMaxFrames|int|1||
|rtx.captureEnableMultiframe|bool|False|Enables multi\-frame capturing\. THIS HAS NOT BEEN MAINTAINED AND SHOULD BE USED WITH EXTREME CAUTION\.|
|rtx.captureFramesPerSecond|int|24|Playback rate marked in the USD stage\.<br>Will eventually determine frequency with which game state is captured and written\. Currently every frame \-\- even those at higher frame rates \-\- are recorded\.|
|rtx.captureInstances|bool|True|If true, an instanced snapshot of the game scene will be captured and exported to a USD stage, in addition to all meshes, textures, materials, etc\.<br>If false, only meshes, etc will be captured\.|
|rtx.captureMaxFrames|int|1|Max frames capturable when running a multi\-frame capture\. The capture can be toggled to completion manually\.|
|rtx.captureMeshBlendWeightDelta|float|0.01|Inter\-frame blend weight min delta warrants new time sample\.|
|rtx.captureMeshColorDelta|float|0.3|Inter\-frame color min delta warrants new time sample\.|
|rtx.captureMeshNormalDelta|float|0.3|Inter\-frame normal min delta warrants new time sample\.|
|rtx.captureMeshPositionDelta|float|0.3|Inter\-frame position min delta warrants new time sample\.|
|rtx.captureMeshTexcoordDelta|float|0.3|Inter\-frame texcoord min delta warrants new time sample\.|
|rtx.captureNoInstance|bool|False||
|rtx.captureNoInstance|bool|False|Same as 'rtx\.captureInstances' except inverse\. This is the original/old variant, and will be deprecated, however is still functional\.|
|rtx.captureShowMenuOnHotkey|bool|True|If true, then the capture menu will appear whenever one of the capture hotkeys are pressed\. A capture MUST be started by using a button in the menu, in that case\.<br>If false, the hotkeys behave as expected\. The user must manually open the menu in order to change any values\.|
|rtx.compositePrimaryDirectDiffuse|bool|True|Enables direct lightning's diffuse signal for primary surfaces in the final composite\.|
|rtx.compositePrimaryDirectSpecular|bool|True|Enables direct lightning's specular signal for primary surfaces in the final composite\.|
|rtx.compositePrimaryIndirectDiffuse|bool|True|Enables indirect lightning's diffuse signal for primary surfaces in the final composite\.|
Expand Down Expand Up @@ -626,7 +629,8 @@ Tables below enumerate all the options and their defaults set by RTX Remix. Note
|rtx.baseGameModPathRegex|string||Regex used to redirect RTX Remix Runtime to another path for replacements and rtx\.conf\.|
|rtx.baseGameModRegex|string||Regex used to determine if the base game is running a mod, like a sourcemod\.|
|rtx.beamTextures|hash set||Textures on draw calls that are already particles or emissively blended and have beam\-like geometry\.<br>Typically objects marked as particles or objects using emissive blending will be rendered with a special method which allows re\-orientation of the billboard geometry assumed to make up the draw call in indirect rays \(reflections for example\)\.<br>This method works fine for typical particles, but some \(e\.g\. a laser beam\) may not be well\-represented with the typical billboard assumption of simply needing to rotate around its centroid to face the view direction\.<br>To handle such cases a different beam mode is used to treat objects as more of a cylindrical beam and re\-orient around its main spanning axis, allowing for better rendering of these beam\-like effect objects\.|
|rtx.captureInstanceStageName|string|||
|rtx.captureInstanceStageName|string|capture_{timestamp}.usd|Name of the 'instance' stage \(see: 'rtx\.captureInstances'\)|
|rtx.captureTimestampReplacement|string|{timestamp}|String that can be used for auto\-replacing current time stamp in instance stage name|
|rtx.cutoutTextures|hash set|||
|rtx.decalTextures|hash set||Textures on draw calls used for static geometric decals or decals with complex topology\.<br>These materials will be blended over the materials underneath them when decal material blending is enabled\.<br>A small configurable offset is applied to each flat/co\-planar part of these decals to prevent coplanar geometric cases \(which poses problems for ray tracing\)\.|
|rtx.dynamicDecalTextures|hash set||Textures on draw calls used for dynamically spawned geometric decals, such as bullet holes\.<br>These materials will be blended over the materials underneath them when decal material blending is enabled\.<br>A small configurable offset is applied to each quad part of these decals to prevent coplanar geometric cases \(which poses problems for ray tracing\)\.|
Expand Down
78 changes: 46 additions & 32 deletions scripts-common/diff_game_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,72 +135,86 @@ def __compareUsd(self, goldenAttr, otherAttr):
goldenVal = pathlib.Path(str(goldenVal)).name
otherVal = "".join(str(goldenVal).split("@"))
otherVal = pathlib.Path(str(goldenVal)).name
if not self.__compareValues(goldenType, goldenVal, otherVal):
if not self.__compareValues(str(goldenType), goldenVal, otherVal):
self.goldenVal = goldenVal
self.otherVal = otherVal
return CaptureDiff.Result.Diff
return CaptureDiff.Result.Success

def __compareValues(self, type, goldenVal, otherVal):
if type.isArray:
if self.__is_array(goldenVal, type):
return self.__compare_array(type, goldenVal, otherVal)
else:
return self.__compare_scalar(type, goldenVal, otherVal)

def __compare_array(self, type, goldenArray, otherArray):
if not CaptureDiff.Attribute.__are_not_none(goldenArray, otherArray):
if CaptureDiff.Attribute.__are_valid_none(goldenArray, otherArray):
if not self.__are_not_none(goldenArray, otherArray):
if self.__are_valid_none(goldenArray, otherArray):
return True
else:
return False
if len(goldenArray) != len(otherArray):
return False
if len(goldenArray) == 0:
return True
bMemberIsArray = False
if CaptureDiff.Attribute.__is_py_array(goldenArray[0]):
bMemberIsArray = True
bMembersMatch = True
for i in range(len(goldenArray)):
if bMemberIsArray:
bMembersMatch = bMembersMatch and self.__compare_array(type, goldenArray[i], otherArray[i])
else:
scalarType = str(type).split("[]")[0]
bMembersMatch = bMembersMatch and self.__compare_scalar(scalarType, goldenArray[i], otherArray[i])
return bMembersMatch
memberType = self.__parse_member_type(type)
bMemberIsArray = self.__is_array(goldenArray[0],memberType)
if(bMemberIsArray):
bMembersMatch = True
for i in range(len(goldenArray)):
bMembersMatch = bMembersMatch and \
self.__compare_array(memberType, goldenArray[i], otherArray[i])
return bMembersMatch
else:
bMembersMatch = True
for i in range(len(goldenArray)):
bMembersMatch = bMembersMatch and \
self.__compare_scalar(memberType, goldenArray[i], otherArray[i])
return bMembersMatch

def __compare_scalar(self, type, goldenScalar, otherScalar):
if not CaptureDiff.Attribute.__are_not_none(goldenScalar, otherScalar):
return CaptureDiff.Attribute.__are_valid_none(goldenScalar, otherScalar)
if CaptureDiff.Attribute.__is_float_type(type):
return CaptureDiff.Attribute.__float_diff(goldenScalar, otherScalar)
if not self.__are_not_none(goldenScalar, otherScalar):
return self.__are_valid_none(goldenScalar, otherScalar)
if self.__is_float_type(type):
return self.__float_diff(goldenScalar, otherScalar)
return goldenScalar == otherScalar

@staticmethod
def __is_py_array(array):
return hasattr(array, "__len__") and not isinstance(array, str)
def __is_array(self, potentialArray, memberType):
return self.__is_py_array(potentialArray) or \
(memberType.find("[]") > -1) or \
(memberType.find("3f") > -1) or \
(memberType.find("2f") > -1) or \
(memberType.find("float3") > -1) or \
(memberType.find("double3") > -1)

@staticmethod
def __are_not_none(golden, other):
def __is_py_array(self, potentialArray):
return hasattr(potentialArray, "__len__") and not isinstance(potentialArray, str)

def __parse_member_type(self, arrayType):
if str(arrayType).find("[]") > -1:
return str(type).split("[]")[0]
if (str(arrayType).find("3f") > -1) or \
(str(arrayType).find("2f") > 1) or \
arrayType :
return "float"

def __are_not_none(self, golden, other):
return golden is not None and other is not None

@staticmethod
def __are_valid_none(golden, other):
def __are_valid_none(self, golden, other):
return golden is None and other is None

@staticmethod
def __is_float_type(type):
def __is_float_type(self, type):
return type == "float" or type == "texCoord2f" or type == "normal3f"

@staticmethod
def __float_diff(golden, other):
def __float_diff(self, golden, other):
if numpy.isnan(golden) or numpy.isnan(other):
return numpy.isnan(golden) and numpy.isnan(other)
absDiff = abs(golden-other)
if(golden != 0):
return abs(absDiff / golden) < CaptureDiff.floatTolerance
return abs(absDiff / golden) <= CaptureDiff.floatTolerance
else:
return absDiff < CaptureDiff.floatTolerance
return absDiff <= CaptureDiff.floatTolerance

def __print_errors(self):
missingPrims = []
Expand Down
17 changes: 11 additions & 6 deletions src/d3d9/d3d9_swapchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
#include "d3d9_swapchain.h"
#include "d3d9_surface.h"
#include "d3d9_monitor.h"

#include "d3d9_hud.h"

#include "../dxvk/dxvk_device.h"
#include "../dxvk/dxvk_objects.h"
#include "../util/util_env.h"
#include "../dxvk/rtx_render/rtx_bridge_message_channel.h"
#include "../dxvk/dxvk_scoped_annotation.h"
Expand Down Expand Up @@ -187,7 +189,8 @@ namespace dxvk {

if (BridgeMessageChannel::get().init(window, D3D9WindowProc)) {
// Send the initial state messages
swapchain->getGUI()->switchMenu(RtxOptions::Get()->showUI(), true);
auto& gui = swapchain->getDxvkDevice()->getCommon()->getImgui();
gui.switchMenu(RtxOptions::Get()->showUI(), true);
} else {
Logger::err("Unable to init bridge message channel. FSE and input capture may not work!");
}
Expand Down Expand Up @@ -249,7 +252,10 @@ namespace dxvk {
PostMessageW(window, WM_ACTIVATEAPP, 1, GetCurrentThreadId());
}

windowData.swapchain->getGUI()->wndProcHandler(window, message, wParam, lParam);
auto& gui = windowData.swapchain->getDxvkDevice()->getCommon()->getImgui();
if(gui.isInit()) {
gui.wndProcHandler(window, message, wParam, lParam);
}

if (!present_parms.Windowed && env::isRemixBridgeActive()) {
FSEState state = ProcessFullscreenExclusiveMessages(window, message, wParam, lParam);
Expand Down Expand Up @@ -1125,8 +1131,8 @@ namespace dxvk {
if (m_hud != nullptr)
m_hud->render(m_context, info.format, info.imageExtent);

if (m_imgui != nullptr)
m_imgui->render(m_window, m_context, info.format, info.imageExtent, m_vsync);
auto& gui = m_device->getCommon()->getImgui();
gui.render(m_window, m_context, info.format, info.imageExtent, m_vsync);

// NV-DXVK start
m_parent->m_rtx.OnPresent(m_imageViews.at(imageIndex)->image());
Expand Down Expand Up @@ -1446,7 +1452,6 @@ namespace dxvk {

void D3D9SwapChainEx::CreateHud() {
m_hud = hud::Hud::createHud(m_device);
m_imgui = ImGUI::createGUI(m_device.ptr(), m_window);

if (m_hud != nullptr) {
m_hud->addItem<hud::HudClientApiItem>("api", 1, GetApiName());
Expand Down
7 changes: 4 additions & 3 deletions src/d3d9/d3d9_swapchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,6 @@ namespace dxvk {
float GetWidthScale() const { return m_widthScale; }
float GetHeightScale() const { return m_heightScale; }

Rc<ImGUI> getGUI() const { return m_imgui; }

// NV-DXVK start: App Controlled FSE
bool AcquireFullscreenExclusive() const {
return GetPresenter()->acquireFullscreenExclusive() == VK_SUCCESS;
Expand All @@ -119,6 +117,10 @@ namespace dxvk {

void SyncFrameLatency();

Rc<DxvkDevice> getDxvkDevice() {
return m_device;
}

private:

enum BindingIds : uint32_t {
Expand Down Expand Up @@ -147,7 +149,6 @@ namespace dxvk {
// NV-DXVK end

Rc<hud::Hud> m_hud;
Rc<ImGUI> m_imgui;

std::vector<Com<D3D9Surface, false>> m_backBuffers;

Expand Down
19 changes: 17 additions & 2 deletions src/dxvk/dxvk_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "rtx_render/rtx_texture_manager.h"
#include "rtx_render/rtx_scene_manager.h"
#include "rtx_render/rtx_reflex.h"
#include "rtx_render/rtx_game_Capturer.h"

#include "rtx_render/rtx_denoise_type.h"
#include "../util/util_lazy.h"
Expand All @@ -57,6 +58,7 @@ namespace dxvk {
class DebugView;
class DxvkPostFx;
class OpacityMicromapManager;
class ImGUI;

class NGXContext;

Expand All @@ -74,7 +76,8 @@ namespace dxvk {
m_sceneManager (device),
m_rtResources (device),
m_rtInitializer (device),
m_textureManager(device),
m_textureManager (device),
m_imgui (device),
m_dummyResources (device),
m_volumeIntegrate(device),
m_volumeFilter(device),
Expand Down Expand Up @@ -107,7 +110,8 @@ namespace dxvk {
m_bloom(device),
m_geometryUtils(device),
m_imageUtils(device),
m_postFx(device) {
m_postFx(device),
m_capturer(new GameCapturer(device, m_sceneManager, m_exporter.get())) {
}

DxvkMemoryAllocator& memoryManager() {
Expand Down Expand Up @@ -301,6 +305,10 @@ namespace dxvk {
RtxTextureManager& getTextureManager() {
return m_textureManager;
}

ImGUI& getImgui() {
return m_imgui;
}

const OpacityMicromapManager* getOpacityMicromapManager() {
return m_sceneManager.getOpacityMicromapManager();
Expand All @@ -314,6 +322,10 @@ namespace dxvk {
return m_exporter.get();
}

Rc<GameCapturer> capturer() {
return m_capturer;
}

void onDestroy() {
getRtxInitializer().onDestroy();

Expand Down Expand Up @@ -360,6 +372,9 @@ namespace dxvk {
Resources m_rtResources;
RtxInitializer m_rtInitializer;
RtxTextureManager m_textureManager;
ImGUI m_imgui;
Rc<GameCapturer> m_capturer;


// RTX Shaders
Active<DxvkVolumeIntegrate> m_volumeIntegrate;
Expand Down
Loading

0 comments on commit a320d8e

Please sign in to comment.