diff --git a/CMakeLists.txt b/CMakeLists.txt index f1227d61..401681e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,11 @@ if(BUILD_TESTING) COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_e6_s3_releases.py" -v) + add_test( + NAME trackeditor-graphics-settings-contract + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_graphics_settings.py" + -v) endif() if(TARGET TrackEditor) diff --git a/TrackEditor/CMakeLists.txt b/TrackEditor/CMakeLists.txt index be723601..719323c5 100644 --- a/TrackEditor/CMakeLists.txt +++ b/TrackEditor/CMakeLists.txt @@ -62,6 +62,9 @@ set(trackeditor_sources GlobalTrackSettings.cpp GlobalTrackSettings.h GlobalTrackSettings.ui + GraphicsDialog.cpp + GraphicsDialog.h + GraphicsDialog.ui LogDialog.cpp LogDialog.h LogDialog.ui diff --git a/TrackEditor/EditorRenderQueue.h b/TrackEditor/EditorRenderQueue.h index a4f4cc47..6e7ff020 100644 --- a/TrackEditor/EditorRenderQueue.h +++ b/TrackEditor/EditorRenderQueue.h @@ -88,6 +88,11 @@ struct tEdRenderRequest // pointer payload, so the worker never reads UI-owned storage. tEdOverlayState Overlay = {}; bool bHasOverlay = false; + // Graphics settings are process-wide renderer state, but each command + // carries the revision it was queued against. The worker applies a changed + // revision before touching the scene and never reads UI-owned storage. + tEdGraphicsSettings GraphicsSettings = {}; + uint64_t ullGraphicsSettingsRevision = 0; // Only set when the reference mesh actually changed: uploading it on every // camera nudge would copy the whole model through the queue each frame. tEdReferenceMeshPayload ReferenceMesh; diff --git a/TrackEditor/EditorRenderService.cpp b/TrackEditor/EditorRenderService.cpp index 4d076bc2..8a46d9f3 100644 --- a/TrackEditor/EditorRenderService.cpp +++ b/TrackEditor/EditorRenderService.cpp @@ -62,6 +62,7 @@ class CEditorRenderThread : public QThread , m_bInitAttempted(false) , m_eInitResult(ROLLER_ED_RESULT_NOT_INITIALIZED) , m_ullActiveDocumentId(0) + , m_ullAppliedGraphicsSettingsRevision(0) { } @@ -348,6 +349,21 @@ class CEditorRenderThread : public QThread return Result; } + if (Request.ullGraphicsSettingsRevision != 0 + && Request.ullGraphicsSettingsRevision + != m_ullAppliedGraphicsSettingsRevision) { + AssertWorkerThread("RollerEd_SetGraphicsSettings"); + const eRollerEdResult eGraphicsResult = + RollerEd_SetGraphicsSettings(&Request.GraphicsSettings); + if (eGraphicsResult != ROLLER_ED_RESULT_OK) { + Result.bLoadFailed = bLoadCommand; + SetFacadeFailure(Result, eGraphicsResult); + return Result; + } + m_ullAppliedGraphicsSettingsRevision = + Request.ullGraphicsSettingsRevision; + } + tEdGeometrySizes Sizes = {}; if (Request.eKind == eEdRenderCommandKind::UNLOAD) { AssertWorkerThread("RollerEd_UnloadTrack for empty document"); @@ -554,13 +570,25 @@ class CEditorRenderThread : public QThread eRollerEdResult m_eInitResult; std::string m_sInitError; uint64_t m_ullActiveDocumentId; + uint64_t m_ullAppliedGraphicsSettingsRevision; }; CEditorRenderService::CEditorRenderService(const QString &sAssetRoot, QObject *pParent) : QObject(pParent) , m_pThread(new CEditorRenderThread(this, EncodePath(sAssetRoot))) + , m_ullGraphicsSettingsRevision(1) { Q_ASSERT(!sAssetRoot.isEmpty()); + m_GraphicsSettings = {}; + m_GraphicsSettings.uiStructSize = sizeof(m_GraphicsSettings); + m_GraphicsSettings.uiVersion = ROLLER_ED_GRAPHICS_SETTINGS_VERSION; + m_GraphicsSettings.eRenderer = ROLLER_ED_RENDERER_GPU; + m_GraphicsSettings.eSoftwareDisplay = ROLLER_ED_SOFTWARE_DISPLAY_SVGA; + m_GraphicsSettings.eAntiAliasing = ROLLER_ED_ANTI_ALIASING_OFF; + m_GraphicsSettings.eAnisotropy = ROLLER_ED_ANISOTROPY_16X; + m_GraphicsSettings.eTextureFilter = ROLLER_ED_TEXTURE_FILTER_NEAREST; + m_GraphicsSettings.fDrawDistanceFraction = 1.0f; + m_GraphicsSettings.uiEmulateTransparentBorders = 1u; } CEditorRenderService::~CEditorRenderService() @@ -584,6 +612,16 @@ void CEditorRenderService::Stop() m_pThread->StopAndWait(); } +void CEditorRenderService::SetGraphicsSettings( + const tEdGraphicsSettings &Settings) +{ + Q_ASSERT(QThread::currentThread() == thread()); + m_GraphicsSettings = Settings; + if (m_ullGraphicsSettingsRevision == std::numeric_limits::max()) + std::terminate(); + ++m_ullGraphicsSettingsRevision; +} + void CEditorRenderService::RegisterDocument(uint64_t ullDocumentId) { Q_ASSERT(QThread::currentThread() == thread()); @@ -625,6 +663,8 @@ uint64_t CEditorRenderService::EnqueueLoadAndRender( Request.bHasCamera = true; Request.Overlay = Overlay; Request.bHasOverlay = true; + Request.GraphicsSettings = m_GraphicsSettings; + Request.ullGraphicsSettingsRevision = m_ullGraphicsSettingsRevision; Request.uiWidth = static_cast(NormalizedSize.width()); Request.uiHeight = static_cast(NormalizedSize.height()); Request.dDevicePixelRatio = dDevicePixelRatio; @@ -656,6 +696,8 @@ uint64_t CEditorRenderService::EnqueueSerializedLoadAndRender( Request.bHasCamera = true; Request.Overlay = Overlay; Request.bHasOverlay = true; + Request.GraphicsSettings = m_GraphicsSettings; + Request.ullGraphicsSettingsRevision = m_ullGraphicsSettingsRevision; Request.uiWidth = static_cast(NormalizedSize.width()); Request.uiHeight = static_cast(NormalizedSize.height()); Request.dDevicePixelRatio = dDevicePixelRatio; @@ -683,6 +725,8 @@ uint64_t CEditorRenderService::EnqueueRender( Request.Tag.uiExpectedGeometryEpoch = uiExpectedGeometryEpoch; Request.Tag.uiFlags = ROLLER_ED_REQUEST_HAS_EXPECTED_EPOCH; Request.eKind = eEdRenderCommandKind::RENDER_ONLY; + Request.GraphicsSettings = m_GraphicsSettings; + Request.ullGraphicsSettingsRevision = m_ullGraphicsSettingsRevision; Request.Camera = Camera; Request.bHasCamera = true; Request.Overlay = Overlay; @@ -713,6 +757,8 @@ uint64_t CEditorRenderService::EnqueueUnload( Request.Tag.ullDocumentId = ullDocumentId; Request.Tag.ullDocumentRevision = ullDocumentRevision; Request.eKind = eEdRenderCommandKind::UNLOAD; + Request.GraphicsSettings = m_GraphicsSettings; + Request.ullGraphicsSettingsRevision = m_ullGraphicsSettingsRevision; const uint64_t ullRequestId = Request.Tag.ullRequestId; m_pThread->Enqueue(std::move(Request)); return ullRequestId; diff --git a/TrackEditor/EditorRenderService.h b/TrackEditor/EditorRenderService.h index 99384542..3df85264 100644 --- a/TrackEditor/EditorRenderService.h +++ b/TrackEditor/EditorRenderService.h @@ -41,6 +41,7 @@ class CEditorRenderService : public QObject void Start(); void Stop(); + void SetGraphicsSettings(const tEdGraphicsSettings &Settings); void RegisterDocument(uint64_t ullDocumentId); void InvalidateDocument(uint64_t ullDocumentId); @@ -93,6 +94,8 @@ class CEditorRenderService : public QObject CEditorRenderThread *m_pThread; std::unordered_set m_RegisteredDocuments; + tEdGraphicsSettings m_GraphicsSettings; + uint64_t m_ullGraphicsSettingsRevision; }; #endif diff --git a/TrackEditor/GraphicsDialog.cpp b/TrackEditor/GraphicsDialog.cpp new file mode 100644 index 00000000..bb212a3c --- /dev/null +++ b/TrackEditor/GraphicsDialog.cpp @@ -0,0 +1,79 @@ +#include "TrackEditor.h" +#include "GraphicsDialog.h" +#include "MainWindow.h" + +CGraphicsDialog::CGraphicsDialog(QWidget *pParent) + : QDialog(pParent) +{ + setupUi(this); + + slDrawDistance->setValue(g_pMainWindow->m_graphics.iDrawDistancePercent); + lblDrawDistanceValue->setText( + QString("%1%").arg(g_pMainWindow->m_graphics.iDrawDistancePercent)); + ckHardwareRendering->setChecked( + g_pMainWindow->m_graphics.bHardwareRendering); + cbSoftwareDisplay->setCurrentIndex( + g_pMainWindow->m_graphics.iSoftwareDisplay); + cbAntiAliasing->setCurrentIndex(g_pMainWindow->m_graphics.iAntiAliasing); + cbAnisotropy->setCurrentIndex(g_pMainWindow->m_graphics.iAnisotropy); + cbTextureFilter->setCurrentIndex(g_pMainWindow->m_graphics.iTextureFilter); + ckTrilinear->setChecked(g_pMainWindow->m_graphics.bTrilinear); + dsbLodBias->setValue(g_pMainWindow->m_graphics.dLodBias); + ckEmulateTransparentBorders->setChecked( + g_pMainWindow->m_graphics.bEmulateTransparentBorders); + + connect(pbClose, &QPushButton::clicked, this, &CGraphicsDialog::reject); + connect(slDrawDistance, &QSlider::valueChanged, + this, &CGraphicsDialog::DialogEdited); + connect(ckHardwareRendering, &QCheckBox::toggled, + this, &CGraphicsDialog::HardwareRenderingToggled); + connect(cbSoftwareDisplay, &QComboBox::currentIndexChanged, + this, &CGraphicsDialog::DialogEdited); + connect(cbAntiAliasing, &QComboBox::currentIndexChanged, + this, &CGraphicsDialog::DialogEdited); + connect(cbAnisotropy, &QComboBox::currentIndexChanged, + this, &CGraphicsDialog::DialogEdited); + connect(cbTextureFilter, &QComboBox::currentIndexChanged, + this, &CGraphicsDialog::DialogEdited); + connect(ckTrilinear, &QCheckBox::toggled, + this, &CGraphicsDialog::DialogEdited); + connect(dsbLodBias, &QDoubleSpinBox::valueChanged, + this, &CGraphicsDialog::DialogEdited); + connect(ckEmulateTransparentBorders, &QCheckBox::toggled, + this, &CGraphicsDialog::DialogEdited); + + UpdateHardwareControls(); +} + +CGraphicsDialog::~CGraphicsDialog() = default; + +void CGraphicsDialog::HardwareRenderingToggled(bool bChecked) +{ + (void)bChecked; + UpdateHardwareControls(); + DialogEdited(); +} + +void CGraphicsDialog::DialogEdited() +{ + g_pMainWindow->m_graphics.iDrawDistancePercent = slDrawDistance->value(); + lblDrawDistanceValue->setText(QString("%1%").arg(slDrawDistance->value())); + g_pMainWindow->m_graphics.bHardwareRendering = + ckHardwareRendering->isChecked(); + g_pMainWindow->m_graphics.iSoftwareDisplay = + cbSoftwareDisplay->currentIndex(); + g_pMainWindow->m_graphics.iAntiAliasing = cbAntiAliasing->currentIndex(); + g_pMainWindow->m_graphics.iAnisotropy = cbAnisotropy->currentIndex(); + g_pMainWindow->m_graphics.iTextureFilter = cbTextureFilter->currentIndex(); + g_pMainWindow->m_graphics.bTrilinear = ckTrilinear->isChecked(); + g_pMainWindow->m_graphics.dLodBias = dsbLodBias->value(); + g_pMainWindow->m_graphics.bEmulateTransparentBorders = + ckEmulateTransparentBorders->isChecked(); + g_pMainWindow->ApplyGraphicsSettings(); +} + +void CGraphicsDialog::UpdateHardwareControls() +{ + gbHardwareOptions->setEnabled(ckHardwareRendering->isChecked()); + gbSoftwareOptions->setEnabled(!ckHardwareRendering->isChecked()); +} diff --git a/TrackEditor/GraphicsDialog.h b/TrackEditor/GraphicsDialog.h new file mode 100644 index 00000000..01d66600 --- /dev/null +++ b/TrackEditor/GraphicsDialog.h @@ -0,0 +1,22 @@ +#ifndef _TRACKEDITOR_GRAPHICSDIALOG_H +#define _TRACKEDITOR_GRAPHICSDIALOG_H + +#include "ui_GraphicsDialog.h" + +class CGraphicsDialog : public QDialog, private Ui::GraphicsDialog +{ + Q_OBJECT + +public: + explicit CGraphicsDialog(QWidget *pParent); + ~CGraphicsDialog() override; + +private slots: + void HardwareRenderingToggled(bool bChecked); + void DialogEdited(); + +private: + void UpdateHardwareControls(); +}; + +#endif diff --git a/TrackEditor/GraphicsDialog.ui b/TrackEditor/GraphicsDialog.ui new file mode 100644 index 00000000..a7d65541 --- /dev/null +++ b/TrackEditor/GraphicsDialog.ui @@ -0,0 +1,209 @@ + + + GraphicsDialog + + + + 0 + 0 + 390 + 445 + + + + Graphics + + + + + + Graphics Settings + + + + + + Draw Dist + + + + + + + + + 100 + + + 10 + + + 100 + + + Qt::Horizontal + + + + + + + 380 + + + 100% + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Hardware Rendering + + + true + + + + + + + + + + Software Rendering Options + + + + + + Display + + + + + + 1 + VGA + SVGA + + + + + + + + + Hardware Rendering Options + + + + + + Anti-aliasing + + + + + + Off + MSAA 2x + MSAA 4x + MSAA 8x + + + + + + Anisotropy + + + + + + 2x + 4x + 8x + 16x + + + + + + Texture Filter + + + + + + Nearest + Bilinear + Anisotropic + + + + + + Trilinear filtering + + + + + + + LOD bias + + + + + + 1 + -4.000000000000000 + 4.000000000000000 + 0.100000000000000 + + + + + + Emulate transparent borders + + + true + + + + + + + + + Qt::Vertical + 2020 + + + + + + + Qt::Horizontal + 4020 + + + + + Close + + + + + + + + + diff --git a/TrackEditor/MainWindow.cpp b/TrackEditor/MainWindow.cpp index d79d9c87..331184b5 100644 --- a/TrackEditor/MainWindow.cpp +++ b/TrackEditor/MainWindow.cpp @@ -28,6 +28,7 @@ #include "Logging.h" #include "NewTrackDialog.h" #include "PreferencesDialog.h" +#include "GraphicsDialog.h" #include "AssignBacksDialog.h" #include "EditorCameraController.h" #include "qtimer.h" @@ -69,6 +70,20 @@ tPreferences::tPreferences() //------------------------------------------------------------------------------------------------- +tGraphicsPreferences::tGraphicsPreferences() + : iDrawDistancePercent(100) + , bHardwareRendering(true) + , iSoftwareDisplay(1) + , iAntiAliasing(0) + , iAnisotropy(3) + , iTextureFilter(0) + , bTrilinear(false) + , dLodBias(0.0) + , bEmulateTransparentBorders(true) +{ }; + +//------------------------------------------------------------------------------------------------- + class CMainWindowPrivate { public: @@ -222,6 +237,7 @@ CMainWindow::CMainWindow(const QString &sAppPath, float fDesktopScale, connect(pBacksAction, &QAction::triggered, this, &CMainWindow::OnBacks); connect(p->m_pDebugAction, &QAction::triggered, this, &CMainWindow::OnDebug); connect(actPreferences, &QAction::triggered, this, &CMainWindow::OnPreferences); + connect(actGraphics, &QAction::triggered, this, &CMainWindow::OnGraphics); connect(actAbout, &QAction::triggered, this, &CMainWindow::OnAbout); connect(twViewer, &QTabWidget::tabCloseRequested, this, &CMainWindow::OnTabCloseRequested); connect(twViewer, &QTabWidget::currentChanged, this, &CMainWindow::OnTabChanged); @@ -1034,6 +1050,14 @@ void CMainWindow::OnPreferences() //------------------------------------------------------------------------------------------------- +void CMainWindow::OnGraphics() +{ + CGraphicsDialog dlg(this); + dlg.exec(); +} + +//------------------------------------------------------------------------------------------------- + void CMainWindow::OnDebug() { p->m_logDialog.raise(); @@ -1377,6 +1401,35 @@ void CMainWindow::LoadSettings() m_preferences.bPasteSigns = settings.value("paste_signs", m_preferences.bPasteSigns).toBool(); m_preferences.bPasteAudio = settings.value("paste_audio", m_preferences.bPasteAudio).toBool(); + //graphics + m_graphics.iDrawDistancePercent = qBound( + 0, settings.value("graphics_draw_distance", + m_graphics.iDrawDistancePercent).toInt(), 100); + m_graphics.bHardwareRendering = settings.value( + "graphics_hardware_rendering", + m_graphics.bHardwareRendering).toBool(); + m_graphics.iSoftwareDisplay = qBound( + 0, settings.value("graphics_software_display", + m_graphics.iSoftwareDisplay).toInt(), 1); + m_graphics.iAntiAliasing = qBound( + 0, settings.value("graphics_antialiasing", + m_graphics.iAntiAliasing).toInt(), 3); + m_graphics.iAnisotropy = qBound( + 0, settings.value("graphics_anisotropy", + m_graphics.iAnisotropy).toInt(), 3); + m_graphics.iTextureFilter = qBound( + 0, settings.value("graphics_texture_filter", + m_graphics.iTextureFilter).toInt(), 2); + m_graphics.bTrilinear = settings.value( + "graphics_trilinear", m_graphics.bTrilinear).toBool(); + m_graphics.dLodBias = qBound( + -4.0, settings.value("graphics_lod_bias", + m_graphics.dLodBias).toDouble(), 4.0); + m_graphics.bEmulateTransparentBorders = settings.value( + "graphics_emulate_transparent_borders", + m_graphics.bEmulateTransparentBorders).toBool(); + ApplyGraphicsSettings(); + show(); } @@ -1422,6 +1475,49 @@ void CMainWindow::SaveSettings() settings.setValue("paste_draw_order", m_preferences.bPasteDrawOrder); settings.setValue("paste_signs", m_preferences.bPasteSigns); settings.setValue("paste_audio", m_preferences.bPasteAudio); + settings.setValue("graphics_draw_distance", m_graphics.iDrawDistancePercent); + settings.setValue("graphics_hardware_rendering", + m_graphics.bHardwareRendering); + settings.setValue("graphics_software_display", m_graphics.iSoftwareDisplay); + settings.setValue("graphics_antialiasing", m_graphics.iAntiAliasing); + settings.setValue("graphics_anisotropy", m_graphics.iAnisotropy); + settings.setValue("graphics_texture_filter", m_graphics.iTextureFilter); + settings.setValue("graphics_trilinear", m_graphics.bTrilinear); + settings.setValue("graphics_lod_bias", m_graphics.dLodBias); + settings.setValue("graphics_emulate_transparent_borders", + m_graphics.bEmulateTransparentBorders); +} + +//------------------------------------------------------------------------------------------------- + +void CMainWindow::ApplyGraphicsSettings() +{ + if (!m_pRenderService) + return; + + tEdGraphicsSettings Settings = {}; + Settings.uiStructSize = sizeof(Settings); + Settings.uiVersion = ROLLER_ED_GRAPHICS_SETTINGS_VERSION; + Settings.eRenderer = m_graphics.bHardwareRendering + ? ROLLER_ED_RENDERER_GPU : ROLLER_ED_RENDERER_SOFTWARE; + Settings.eSoftwareDisplay = static_cast( + m_graphics.iSoftwareDisplay); + Settings.eAntiAliasing = + static_cast(m_graphics.iAntiAliasing); + Settings.eAnisotropy = + static_cast(m_graphics.iAnisotropy); + Settings.eTextureFilter = + static_cast(m_graphics.iTextureFilter); + Settings.uiTrilinear = m_graphics.bTrilinear ? 1u : 0u; + Settings.uiEmulateTransparentBorders = + m_graphics.bEmulateTransparentBorders ? 1u : 0u; + Settings.fDrawDistanceFraction = + static_cast(m_graphics.iDrawDistancePercent) / 100.0f; + Settings.fLodBias = static_cast(m_graphics.dLodBias); + m_pRenderService->SetGraphicsSettings(Settings); + + if (GetCurrentPreview()) + GetCurrentPreview()->RefreshGraphicsSettings(); } //------------------------------------------------------------------------------------------------- diff --git a/TrackEditor/MainWindow.h b/TrackEditor/MainWindow.h index 539b867d..cb6279e7 100644 --- a/TrackEditor/MainWindow.h +++ b/TrackEditor/MainWindow.h @@ -30,6 +30,21 @@ struct tPreferences }; //------------------------------------------------------------------------------------------------- +struct tGraphicsPreferences +{ + tGraphicsPreferences(); + int iDrawDistancePercent; + bool bHardwareRendering; + int iSoftwareDisplay; + int iAntiAliasing; + int iAnisotropy; + int iTextureFilter; + bool bTrilinear; + double dLodBias; + bool bEmulateTransparentBorders; +}; +//------------------------------------------------------------------------------------------------- + class CMainWindow : public QMainWindow, private Ui::MainWindow { Q_OBJECT @@ -49,10 +64,12 @@ class CMainWindow : public QMainWindow, private Ui::MainWindow float GetDesktopScale() { return m_fDesktopScale; }; CTrack *GetCurrentTrack(); CTrackPreview *GetCurrentPreview(); + void ApplyGraphicsSettings(); QString m_sLastTrackFilesFolder; CQtUserKeyMapper m_keyMapper; tPreferences m_preferences; + tGraphicsPreferences m_graphics; protected: void closeEvent(QCloseEvent *pEvent); @@ -77,6 +94,7 @@ protected slots: void OnDeselect(); void OnBacks(); void OnPreferences(); + void OnGraphics(); void OnDebug(); void OnAbout(); void OnTabCloseRequested(int iIndex); diff --git a/TrackEditor/MainWindow.ui b/TrackEditor/MainWindow.ui index a6e6e798..a3a5de5e 100644 --- a/TrackEditor/MainWindow.ui +++ b/TrackEditor/MainWindow.ui @@ -218,6 +218,7 @@ Settings + @@ -418,6 +419,11 @@ Export to glTF + + + Graphics... + + Export all tracks and cars OBJ diff --git a/TrackEditor/TrackPreview.cpp b/TrackEditor/TrackPreview.cpp index 9b699e23..816fd072 100644 --- a/TrackEditor/TrackPreview.cpp +++ b/TrackEditor/TrackPreview.cpp @@ -590,6 +590,13 @@ void CTrackPreview::Activate() //------------------------------------------------------------------------------------------------- +void CTrackPreview::RefreshGraphicsSettings() +{ + ScheduleCameraRender(); +} + +//------------------------------------------------------------------------------------------------- + void CTrackPreview::MarkDocumentEdited() { m_FrameState.MarkDocumentEdited(); diff --git a/TrackEditor/TrackPreview.h b/TrackEditor/TrackPreview.h index 3f379422..f60d9905 100644 --- a/TrackEditor/TrackPreview.h +++ b/TrackEditor/TrackPreview.h @@ -61,6 +61,7 @@ class CTrackPreview : public QWidget // dialog rather than the file. void UpdateReferenceModelWireframe(bool bWireframe); void Activate(); + void RefreshGraphicsSettings(); void MarkDocumentEdited(); bool CanExport() const { return m_FrameState.CanExport(); } diff --git a/external/ROLLER b/external/ROLLER index bff1d18c..0efa4174 160000 --- a/external/ROLLER +++ b/external/ROLLER @@ -1 +1 @@ -Subproject commit bff1d18c778f2b563b2119b9d42961ded7b884f2 +Subproject commit 0efa4174471cee29bbf4ee487a0b1525533b8450 diff --git a/tests/editor_render_service_test.cpp b/tests/editor_render_service_test.cpp index 629f382f..92ebc4b5 100644 --- a/tests/editor_render_service_test.cpp +++ b/tests/editor_render_service_test.cpp @@ -43,6 +43,8 @@ uint32_t g_uiReferenceMeshCount = 0; uint32_t g_uiReferenceVertexCount = 0; uint32_t g_uiReferenceIndexCount = 0; uint32_t g_uiReferenceFlags = 0; +uint32_t g_uiGraphicsCount = 0; +tEdGraphicsSettings g_LastGraphicsSettings = {}; float g_fReferenceFirstX = 0.0f; float g_fReferenceScaleX = 0.0f; uint32_t g_uiGeometryEpoch = 0; @@ -225,6 +227,18 @@ extern "C" eRollerEdResult ROLLER_ED_CALL RollerEd_SetCamera( return ROLLER_ED_RESULT_OK; } +extern "C" eRollerEdResult ROLLER_ED_CALL RollerEd_SetGraphicsSettings( + const tEdGraphicsSettings *pSettings) +{ + RecordFacadeThread(); + assert(pSettings); + assert(pSettings->uiStructSize == sizeof(*pSettings)); + assert(pSettings->uiVersion == ROLLER_ED_GRAPHICS_SETTINGS_VERSION); + g_LastGraphicsSettings = *pSettings; + ++g_uiGraphicsCount; + return ROLLER_ED_RESULT_OK; +} + extern "C" eRollerEdResult ROLLER_ED_CALL RollerEd_SetReferenceMesh( const tEdReferenceMesh *pMesh) { @@ -289,6 +303,19 @@ int main(int argc, char **argv) g_pUiThreadId = QThread::currentThreadId(); CEditorRenderService Service("test-assets"); + tEdGraphicsSettings Graphics = {}; + Graphics.uiStructSize = sizeof(Graphics); + Graphics.uiVersion = ROLLER_ED_GRAPHICS_SETTINGS_VERSION; + Graphics.eRenderer = ROLLER_ED_RENDERER_SOFTWARE; + Graphics.eSoftwareDisplay = ROLLER_ED_SOFTWARE_DISPLAY_VGA; + Graphics.eAntiAliasing = ROLLER_ED_ANTI_ALIASING_2X; + Graphics.eAnisotropy = ROLLER_ED_ANISOTROPY_8X; + Graphics.eTextureFilter = ROLLER_ED_TEXTURE_FILTER_BILINEAR; + Graphics.uiTrilinear = 1u; + Graphics.fDrawDistanceFraction = 0.75f; + Graphics.fLodBias = -0.5f; + Graphics.uiEmulateTransparentBorders = 0u; + Service.SetGraphicsSettings(Graphics); CDocumentFrameState Document(CEditorRenderIds::NextDocumentId()); Service.RegisterDocument(Document.GetDocumentId()); @@ -318,6 +345,7 @@ int main(int argc, char **argv) Camera.fPosition[0] = 999.0f; Overlay.uiSurfaceClassMask = 0xffffu; Overlay.uiWireframeClassMask = 0xffffu; + Graphics.fDrawDistanceFraction = 0.1f; Service.Start(); const tEdRenderResult GoodResult = WaitForResult(Service, ullGoodRequest); @@ -332,6 +360,19 @@ int main(int argc, char **argv) == ROLLER_ED_OVERLAY_CLASS_BIT(ROLLER_ED_SURFACE_CLASS_ROOF)); assert((g_uiOverlayFlags & ROLLER_ED_OVERLAY_SHOW_SURFACES) != 0); assert(g_uiOverlayCount == 1); + assert(g_uiGraphicsCount == 1); + assert(g_LastGraphicsSettings.eRenderer == ROLLER_ED_RENDERER_SOFTWARE); + assert(g_LastGraphicsSettings.eSoftwareDisplay + == ROLLER_ED_SOFTWARE_DISPLAY_VGA); + assert(g_LastGraphicsSettings.eAntiAliasing + == ROLLER_ED_ANTI_ALIASING_2X); + assert(g_LastGraphicsSettings.eAnisotropy == ROLLER_ED_ANISOTROPY_8X); + assert(g_LastGraphicsSettings.eTextureFilter + == ROLLER_ED_TEXTURE_FILTER_BILINEAR); + assert(g_LastGraphicsSettings.uiTrilinear == 1u); + assert(g_LastGraphicsSettings.fDrawDistanceFraction == 0.75f); + assert(g_LastGraphicsSettings.fLodBias == -0.5f); + assert(g_LastGraphicsSettings.uiEmulateTransparentBorders == 0u); // E3A-S7: no mesh was supplied, so the worker never touched the facade's. assert(g_uiReferenceMeshCount == 0); assert(GoodResult.Tag.eResult == ROLLER_ED_RESULT_OK); diff --git a/tests/test_e2_s1_roller_submodule.py b/tests/test_e2_s1_roller_submodule.py index 2c48f52f..f5b57eda 100644 --- a/tests/test_e2_s1_roller_submodule.py +++ b/tests/test_e2_s1_roller_submodule.py @@ -10,7 +10,7 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[1] ROLLER_ROOT = REPOSITORY_ROOT / "external" / "ROLLER" -EXPECTED_ROLLER_COMMIT = "bff1d18c778f2b563b2119b9d42961ded7b884f2" +EXPECTED_ROLLER_COMMIT = "0efa4174471cee29bbf4ee487a0b1525533b8450" def run_git(*arguments: str, cwd: Path = REPOSITORY_ROOT) -> subprocess.CompletedProcess: diff --git a/tests/test_graphics_settings.py b/tests/test_graphics_settings.py new file mode 100644 index 00000000..da783237 --- /dev/null +++ b/tests/test_graphics_settings.py @@ -0,0 +1,195 @@ +from pathlib import Path +import re +import unittest + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +EDITOR = REPOSITORY_ROOT / "TrackEditor" +ROLLER = REPOSITORY_ROOT / "external" / "ROLLER" / "PROJECTS" / "ROLLER" + + +def function_body(source: str, signature: str) -> str: + start = source.index(signature) + brace = source.index("{", start) + depth = 0 + for position in range(brace, len(source)): + if source[position] == "{": + depth += 1 + elif source[position] == "}": + depth -= 1 + if depth == 0: + return source[start : position + 1] + raise AssertionError(f"function body not closed: {signature}") + + +class GraphicsSettingsTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.window = (EDITOR / "MainWindow.cpp").read_text(encoding="utf-8") + cls.window_ui = (EDITOR / "MainWindow.ui").read_text(encoding="utf-8") + cls.dialog = (EDITOR / "GraphicsDialog.cpp").read_text(encoding="utf-8") + cls.dialog_ui = (EDITOR / "GraphicsDialog.ui").read_text(encoding="utf-8") + cls.service = (EDITOR / "EditorRenderService.cpp").read_text( + encoding="utf-8" + ) + cls.api = (ROLLER / "editor_api.h").read_text(encoding="ascii") + cls.adapter = (ROLLER / "editor_legacy_scene.c").read_text( + encoding="utf-8" + ) + cls.track_draw = (ROLLER / "drawtrk3.c").read_text(encoding="utf-8") + + def test_graphics_action_follows_preferences(self) -> None: + settings_menu = self.window_ui[ + self.window_ui.index('') : + self.window_ui.index("", self.window_ui.index( + '' + )) + ] + self.assertLess( + settings_menu.index('name="actPreferences"'), + settings_menu.index('name="actGraphics"'), + ) + self.assertIn("Graphics...", self.window_ui) + self.assertIn( + "connect(actGraphics, &QAction::triggered, " + "this, &CMainWindow::OnGraphics)", + self.window, + ) + + def test_dialog_contains_the_requested_controls_and_defaults(self) -> None: + for label in ( + "Draw Dist", + "Hardware Rendering", + "Software Rendering Options", + "Display", + "VGA", + "SVGA", + "Anti-aliasing", + "Anisotropy", + "Texture Filter", + "Trilinear filtering", + "LOD bias", + "Emulate transparent borders", + ): + self.assertIn(f"{label}", self.dialog_ui) + defaults = function_body( + self.window, "tGraphicsPreferences::tGraphicsPreferences()" + ) + for expected in ( + "iDrawDistancePercent(100)", + "bHardwareRendering(true)", + "iSoftwareDisplay(1)", + "iAntiAliasing(0)", + "iAnisotropy(3)", + "iTextureFilter(0)", + "bTrilinear(false)", + "dLodBias(0.0)", + "bEmulateTransparentBorders(true)", + ): + self.assertIn(expected, defaults) + self.assertIn('', self.dialog_ui) + self.assertNotIn('name="sbDrawDistance"', self.dialog_ui) + + def test_software_mode_disables_every_hardware_option(self) -> None: + update = function_body( + self.dialog, "void CGraphicsDialog::UpdateHardwareControls()" + ) + self.assertIn( + "gbHardwareOptions->setEnabled(ckHardwareRendering->isChecked())", + update, + ) + self.assertIn( + "gbSoftwareOptions->setEnabled(!ckHardwareRendering->isChecked())", + update, + ) + for control in ( + "cbAntiAliasing", + "cbAnisotropy", + "cbTextureFilter", + "ckTrilinear", + "dsbLodBias", + "ckEmulateTransparentBorders", + ): + hardware_group = self.dialog_ui[ + self.dialog_ui.index( + '' + ) : + self.dialog_ui.index( + '', + self.dialog_ui.index( + '' + ), + ) + ] + self.assertIn(f'name="{control}"', hardware_group) + software_group = self.dialog_ui[ + self.dialog_ui.index( + '' + ) : self.dialog_ui.index( + '', + self.dialog_ui.index( + '' + ), + ) + ] + self.assertIn('name="cbSoftwareDisplay"', software_group) + + def test_all_values_round_trip_through_trackeditor_ini(self) -> None: + keys = ( + "graphics_draw_distance", + "graphics_hardware_rendering", + "graphics_software_display", + "graphics_antialiasing", + "graphics_anisotropy", + "graphics_texture_filter", + "graphics_trilinear", + "graphics_lod_bias", + "graphics_emulate_transparent_borders", + ) + for key in keys: + self.assertGreaterEqual(self.window.count(f'"{key}"'), 2, key) + self.assertIn('m_sAppPath + "/TrackEditor.ini"', self.window) + + def test_settings_are_copied_to_the_worker_and_applied_before_render(self) -> None: + process = function_body( + self.service, + "tEdRenderResult ProcessRequest(const tEdRenderRequest &Request)", + ) + self.assertLess( + process.index("RollerEd_SetGraphicsSettings"), + process.index("RollerEd_LoadTrackFile"), + ) + self.assertIn("ullGraphicsSettingsRevision", self.service) + apply_settings = function_body( + self.window, "void CMainWindow::ApplyGraphicsSettings()" + ) + self.assertIn("m_pRenderService->SetGraphicsSettings(Settings)", apply_settings) + self.assertIn("RefreshGraphicsSettings", apply_settings) + + def test_facade_validates_and_applies_every_renderer_setting(self) -> None: + self.assertIn("tEdGraphicsSettings", self.api) + self.assertIn("RollerEd_SetGraphicsSettings", self.api) + for setter in ( + "game_render_set_antialiasing", + "game_render_set_anisotropy_level", + "game_render_set_texture_filter", + "game_render_set_trilinear", + "game_render_set_lod_bias", + "game_render_set_emulate_software_track_borders", + ): + self.assertIn(setter, self.adapter) + self.assertIn("g_fDrawDistanceFraction", self.adapter) + self.assertIn("eSoftwareDisplay", self.api) + self.assertIn("editor_scene_set_legacy_display", self.adapter) + visibility = function_body( + self.track_draw, "int CalcVisibleTrackEditor(unsigned int uiViewMode)" + ) + self.assertIn("g_fDrawDistanceFraction", visibility) + self.assertIn("TrakView[iCurrChunk].byForwardMainChunks", visibility) + self.assertIn("TrakView[iCurrChunk].byBackwardMainChunks", visibility) + self.assertIn("(TRAK_LEN - 1) - TrackSize", visibility) + self.assertIn("TRAK_LEN - 1", visibility) + + +if __name__ == "__main__": + unittest.main()