From 820f44a71690cb2ce183c92f0fb2940a5ef2c19f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20B=C3=A1lint=20Misius?= Date: Tue, 20 Aug 2024 22:13:32 +0200 Subject: [PATCH] Do rendering on another thread when possible "Possible" here means "when there are no particles with Lua graphics functions that haven't reported that they don't want to be called again and there are no beforesimdraw/aftersimdraw event handlers". I put my trust in Simulation::elementCount here; I sure hope it's always correct. If there is any of these, the renderer thread is paused and rendering happens on the main thread. To be clear, these are the situations in which Lua code is called with LuaScriptInterface::eventTraits having the eventTraitSimGraphics bit set. If the detection of this situation is somehow flawed, there will still be no crashes because when running on the renderer thread, Renderer is told to not call Lua functions, and GameController::BeforeSimDraw/AfterSimDraw is not called by GameView::RendererThread. The worst that can happen is that some particles are not rendered properly. Renderer settings (color and display modes, deco state, finding-element state, etc.) are managed by GameModel in the form of a RendererSettings and are passed to Renderer before each frame. The RenderableSimulation that Renderer works off of is also configured before each frame, either to point to a copy dedicated to the renderer thread, or directly to GameModel's Simulation, if the renderer thread is paused. Similarly, the result of the rendering is managed by GameView in the form of a RendererFrame, to enable e.g. sampling with deco tools without having to pause the renderer thread. Each time GameView::OnDraw is called, it checks whether rendering is allowed to happen on a different thread (see above for conditions). If it is, but the renderer thread is absent, GameView starts it and dispatches it (provides with settings and simulation data) right away. At this point, the renderer thread is definitely rendering a frame. GameView waits for this to finish and exchanges data with the renderer once it's done: it takes the result of the rendering and also dispatches the renderer thread again. This introduces a one-frame delay in rendering, which we can live with. If rendering is not allowed to happen on a different thread, GameView waits for the renderer thread to finish what it's currently doing and pauses it, then proceeds to render the frame by itself. Affecting this condition (whether rendering is allowed to happen on a different) by installing or removing event handlers, or clearing graphics cache (which, note, requires acquiring a unique lock on it), while the renderer thread is working, is not an issue because the renderer thread doesn't bother with event handlers, and because it acquires a shared lock on the graphics cache for rendering. The GameModel's Renderer is thus still primarily managed by GameView, potentially in two different threads, in a strictly non-overlapping manner. The exception to this is when RenderView butts in and starts doing renders of its own; in such cases, the renderer thread must be paused explicitly, as RenderView does by calling GameView::PauseRendererThread. --- src/PowderToyRenderer.cpp | 6 +- src/client/ThumbnailRendererTask.cpp | 2 +- src/graphics/Renderer.h | 65 +--- src/graphics/RendererBasic.cpp | 55 +--- src/graphics/RendererSettings.h | 23 ++ src/gui/game/GameController.cpp | 25 +- src/gui/game/GameController.h | 4 +- src/gui/game/GameModel.cpp | 32 +- src/gui/game/GameModel.h | 6 + src/gui/game/GameView.cpp | 440 +++++++++++++++++---------- src/gui/game/GameView.h | 36 ++- src/gui/preview/PreviewView.cpp | 2 +- src/gui/render/RenderController.cpp | 4 +- src/gui/render/RenderController.h | 3 +- src/gui/render/RenderModel.cpp | 40 +-- src/gui/render/RenderModel.h | 8 +- src/gui/render/RenderView.cpp | 27 +- src/gui/render/RenderView.h | 2 + src/lua/CommandInterface.h | 1 + src/lua/LuaGraphics.cpp | 24 +- src/lua/LuaRenderer.cpp | 21 +- src/lua/LuaScriptInterface.cpp | 39 ++- src/lua/LuaScriptInterface.h | 15 +- src/lua/PlainCommandInterface.cpp | 5 + src/simulation/SaveRenderer.cpp | 16 +- src/simulation/SaveRenderer.h | 3 +- 26 files changed, 526 insertions(+), 378 deletions(-) create mode 100644 src/graphics/RendererSettings.h diff --git a/src/PowderToyRenderer.cpp b/src/PowderToyRenderer.cpp index 5527f4856..fb88865d0 100644 --- a/src/PowderToyRenderer.cpp +++ b/src/PowderToyRenderer.cpp @@ -52,8 +52,10 @@ int main(int argc, char *argv[]) sim->Load(gameSave.get(), true, { 0, 0 }); //Render save - ren->decorations_enable = true; - ren->blackDecorations = true; + RendererSettings rendererSettings; + rendererSettings.decorations_enable = true; + rendererSettings.blackDecorations = true; + ren->ApplySettings(rendererSettings); int frame = 15; while(frame) diff --git a/src/client/ThumbnailRendererTask.cpp b/src/client/ThumbnailRendererTask.cpp index 61c4d64f2..bf03faa3c 100644 --- a/src/client/ThumbnailRendererTask.cpp +++ b/src/client/ThumbnailRendererTask.cpp @@ -29,7 +29,7 @@ ThumbnailRendererTask::~ThumbnailRendererTask() bool ThumbnailRendererTask::doWork() { - thumbnail = SaveRenderer::Ref().Render(save.get(), decorations, fire); + thumbnail = SaveRenderer::Ref().Render(save.get(), decorations, fire, RendererSettings{}); if (thumbnail) { thumbnail->ResizeToFit(size, true); diff --git a/src/graphics/Renderer.h b/src/graphics/Renderer.h index e861b9d3b..e438e88db 100644 --- a/src/graphics/Renderer.h +++ b/src/graphics/Renderer.h @@ -2,10 +2,9 @@ #include "Icons.h" #include "RasterDrawMethods.h" #include "gui/game/RenderPreset.h" -#include "gui/interface/Point.h" +#include "RendererSettings.h" #include "common/tpt-rand.h" #include "RendererFrame.h" -#include "FindingElement.h" #include #include #include @@ -13,13 +12,13 @@ #include struct RenderPreset; -struct RenderableSimulation; class Renderer; +struct RenderableSimulation; struct Particle; struct GraphicsFuncContext { - const Renderer *ren; + const RendererSettings *ren; const RenderableSimulation *sim; RNG rng; const Particle *pipeSubcallCpart; @@ -28,7 +27,7 @@ struct GraphicsFuncContext int HeatToColour(float temp); -class Renderer: public RasterDrawMethods +class Renderer : private RendererSettings, public RasterDrawMethods { RendererFrame video; std::array persistentVideo; @@ -41,39 +40,24 @@ class Renderer: public RasterDrawMethods friend struct RasterDrawMethods; - float fireIntensity = 1; + RNG rng; + unsigned char fire_r[YCELLS][XCELLS]; + unsigned char fire_g[YCELLS][XCELLS]; + unsigned char fire_b[YCELLS][XCELLS]; + unsigned int fire_alpha[CELL*3][CELL*3]; public: - RNG rng; - const RenderableSimulation *sim = nullptr; - const RendererFrame &GetVideo() const { return video; } - uint32_t renderMode = 0; - uint32_t colorMode = 0; - uint32_t displayMode = 0; + const RenderableSimulation *sim = nullptr; + + void ApplySettings(const RendererSettings &newSettings); + static const std::vector renderModePresets; - // - unsigned char fire_r[YCELLS][XCELLS]; - unsigned char fire_g[YCELLS][XCELLS]; - unsigned char fire_b[YCELLS][XCELLS]; - unsigned int fire_alpha[CELL*3][CELL*3]; - // - bool gravityZonesEnabled; - bool gravityFieldEnabled; - int decorations_enable; - bool blackDecorations; - bool debugLines; - std::optional findingElement; - int foundElements; - //Mouse position for debug information - ui::Point mousePos; - - //Renderers void RenderSimulation(); void DrawBlob(Vec2 pos, RGB colour); @@ -81,10 +65,6 @@ public: void DrawSigns(); void render_gravlensing(const RendererFrame &source); void render_fire(); - float GetFireIntensity() const - { - return fireIntensity; - } void prepare_alpha(int size, float intensity); void render_parts(); void draw_grav_zones(); @@ -95,22 +75,6 @@ public: void ClearAccumulation(); void clearScreen(); - void draw_icon(int x, int y, Icon icon); - - //... - //Display mode modifiers - void SetRenderMode(uint32_t newRenderMode); - uint32_t GetRenderMode(); - void SetDisplayMode(uint32_t newDisplayMode); - uint32_t GetDisplayMode(); - void SetColorMode(uint32_t newColorMode); - uint32_t GetColorMode(); - - void ResetModes(); - - int GetGridSize() { return gridSize; } - void SetGridSize(int value) { gridSize = value; } - static std::unique_ptr WallIcon(int wallID, Vec2 size); Renderer(); @@ -140,7 +104,4 @@ public: RENDERER_TABLE(firwTable) #undef RENDERER_TABLE static void PopulateTables(); - -private: - int gridSize; }; diff --git a/src/graphics/RendererBasic.cpp b/src/graphics/RendererBasic.cpp index 606ae9869..879ec3b33 100644 --- a/src/graphics/RendererBasic.cpp +++ b/src/graphics/RendererBasic.cpp @@ -180,15 +180,7 @@ void Renderer::PopulateTables() } } -Renderer::Renderer(): - gravityZonesEnabled(false), - gravityFieldEnabled(false), - decorations_enable(1), - blackDecorations(false), - debugLines(false), - foundElements(0), - mousePos(0, 0), - gridSize(0) +Renderer::Renderer() { PopulateTables(); @@ -197,9 +189,8 @@ Renderer::Renderer(): memset(fire_b, 0, sizeof(fire_b)); //Set defauly display modes - ResetModes(); - prepare_alpha(CELL, 1.0f); + ClearAccumulation(); } void Renderer::ClearAccumulation() @@ -210,51 +201,17 @@ void Renderer::ClearAccumulation() std::fill(persistentVideo.begin(), persistentVideo.end(), 0); } -void Renderer::SetRenderMode(uint32_t newRenderMode) +void Renderer::ApplySettings(const RendererSettings &newSettings) { - int oldRenderMode = renderMode; - renderMode = newRenderMode; - if (!(renderMode & FIREMODE) && (oldRenderMode & FIREMODE)) + if (!(newSettings.renderMode & FIREMODE) && (renderMode & FIREMODE)) { ClearAccumulation(); } -} - -uint32_t Renderer::GetRenderMode() -{ - return renderMode; -} - -void Renderer::SetDisplayMode(uint32_t newDisplayMode) -{ - int oldDisplayMode = displayMode; - displayMode = newDisplayMode; - if (!(displayMode & DISPLAY_PERS) && (oldDisplayMode & DISPLAY_PERS)) + if (!(newSettings.displayMode & DISPLAY_PERS) && (displayMode & DISPLAY_PERS)) { ClearAccumulation(); } -} - -uint32_t Renderer::GetDisplayMode() -{ - return displayMode; -} - -void Renderer::SetColorMode(uint32_t newColorMode) -{ - colorMode = newColorMode; -} - -uint32_t Renderer::GetColorMode() -{ - return colorMode; -} - -void Renderer::ResetModes() -{ - SetRenderMode(RENDER_BASC | RENDER_FIRE | RENDER_SPRK | RENDER_EFFE); - SetDisplayMode(0); - SetColorMode(COLOUR_DEFAULT); + static_cast(*this) = newSettings; } template struct RasterDrawMethods; diff --git a/src/graphics/RendererSettings.h b/src/graphics/RendererSettings.h new file mode 100644 index 000000000..25845b8f8 --- /dev/null +++ b/src/graphics/RendererSettings.h @@ -0,0 +1,23 @@ +#pragma once +#include "gui/interface/Point.h" +#include "simulation/ElementGraphics.h" +#include "FindingElement.h" +#include +#include + +struct RendererSettings +{ + uint32_t renderMode = RENDER_BASC | RENDER_FIRE | RENDER_SPRK | RENDER_EFFE; + uint32_t displayMode = 0; + uint32_t colorMode = COLOUR_DEFAULT; + std::optional findingElement; + bool gravityZonesEnabled = false; + bool gravityFieldEnabled = false; + int decorations_enable = 1; + bool blackDecorations = false; + bool debugLines = false; + int foundElements = 0; + ui::Point mousePos = { 0, 0 }; + int gridSize = 0; + float fireIntensity = 1; +}; diff --git a/src/gui/game/GameController.cpp b/src/gui/game/GameController.cpp index 22d1d4b1e..2b4e91349 100644 --- a/src/gui/game/GameController.cpp +++ b/src/gui/game/GameController.cpp @@ -271,9 +271,9 @@ void GameController::Install() void GameController::AdjustGridSize(int direction) { if(direction > 0) - gameModel->GetRenderer()->SetGridSize((gameModel->GetRenderer()->GetGridSize()+1)%10); + gameModel->GetRendererSettings().gridSize = (gameModel->GetRendererSettings().gridSize+1)%10; else - gameModel->GetRenderer()->SetGridSize((gameModel->GetRenderer()->GetGridSize()+9)%10); + gameModel->GetRendererSettings().gridSize = (gameModel->GetRendererSettings().gridSize+9)%10; } void GameController::InvertAirSim() @@ -851,19 +851,19 @@ void GameController::ToggleNewtonianGravity() void GameController::LoadRenderPreset(int presetNum) { - Renderer * renderer = gameModel->GetRenderer(); + auto &settings = gameModel->GetRendererSettings(); RenderPreset preset = Renderer::renderModePresets[presetNum]; gameModel->SetInfoTip(preset.Name); - renderer->SetRenderMode(preset.renderMode); - renderer->SetDisplayMode(preset.displayMode); - renderer->SetColorMode(preset.colorMode); + settings.renderMode = preset.renderMode; + settings.displayMode = preset.displayMode; + settings.colorMode = preset.colorMode; } void GameController::Update() { auto &sd = SimulationData::CRef(); ui::Point pos = gameView->GetMousePosition(); - gameModel->GetRenderer()->mousePos = PointTranslate(pos); + gameModel->GetRendererSettings().mousePos = PointTranslate(pos); if (pos.X < XRES && pos.Y < YRES) gameView->SetSample(gameModel->GetSimulation()->GetSample(PointTranslate(pos).X, PointTranslate(pos).Y)); else @@ -1106,7 +1106,7 @@ void GameController::SetActiveTool(int toolSelection, Tool * tool) if (gameModel->GetActiveMenu() == SC_DECO && toolSelection == 2) toolSelection = 0; gameModel->SetActiveTool(toolSelection, tool); - gameModel->GetRenderer()->gravityZonesEnabled = false; + gameModel->GetRendererSettings().gravityZonesEnabled = false; if (toolSelection == 3) gameModel->GetSimulation()->replaceModeSelected = tool->ToolID; gameModel->SetLastTool(tool); @@ -1115,7 +1115,7 @@ void GameController::SetActiveTool(int toolSelection, Tool * tool) auto *activeTool = gameModel->GetActiveTool(i); if (activeTool && activeTool->Identifier == "DEFAULT_WL_GRVTY") { - gameModel->GetRenderer()->gravityZonesEnabled = true; + gameModel->GetRendererSettings().gravityZonesEnabled = true; } } if (tool->Identifier == "DEFAULT_UI_PROPERTY") @@ -1404,7 +1404,7 @@ void GameController::HideConsole() void GameController::OpenRenderOptions() { - renderOptions = new RenderController(gameModel->GetSimulation(), gameModel->GetRenderer(), NULL); + renderOptions = new RenderController(gameModel->GetSimulation(), gameModel->GetRenderer(), &gameModel->GetRendererSettings(), NULL); ui::Engine::Ref().ShowWindow(renderOptions->GetView()); } @@ -1727,3 +1727,8 @@ void GameController::AfterSimDraw() { commandInterface->HandleEvent(AfterSimDrawEvent{}); } + +bool GameController::HaveSimGraphicsEventHandlers() +{ + return commandInterface->HaveSimGraphicsEventHandlers(); +} diff --git a/src/gui/game/GameController.h b/src/gui/game/GameController.h index b96305628..94b33652b 100644 --- a/src/gui/game/GameController.h +++ b/src/gui/game/GameController.h @@ -2,6 +2,7 @@ #include "lua/CommandInterfacePtr.h" #include "client/ClientListener.h" #include "client/StartupInfo.h" +#include "common/ExplicitSingleton.h" #include "gui/interface/Point.h" #include "gui/interface/Colour.h" #include "gui/SavePreviewType.h" @@ -37,7 +38,7 @@ class GameSave; class LoginController; class TagsController; class ConsoleController; -class GameController: public ClientListener +class GameController : public ClientListener, public ExplicitSingleton { CommandInterfacePtr commandInterface; @@ -204,4 +205,5 @@ public: void BeforeSimDraw(); void AfterSimDraw(); + bool HaveSimGraphicsEventHandlers(); }; diff --git a/src/gui/game/GameModel.cpp b/src/gui/game/GameModel.cpp index 49800137b..4ef2ab21a 100644 --- a/src/gui/game/GameModel.cpp +++ b/src/gui/game/GameModel.cpp @@ -91,15 +91,15 @@ GameModel::GameModel(): setFunc(*pref); }; handleOldModes("Renderer.RenderMode", "Renderer.RenderModes", RENDER_FIRE | RENDER_EFFE | RENDER_BASC, [this](uint32_t renderMode) { - ren->SetRenderMode(renderMode); + rendererSettings.renderMode = renderMode; }); handleOldModes("Renderer.DisplayMode", "Renderer.DisplayModes", 0, [this](uint32_t displayMode) { - ren->SetDisplayMode(displayMode); + rendererSettings.displayMode = displayMode; }); - ren->SetColorMode(prefs.Get("Renderer.ColourMode", UINT32_C(0))); + rendererSettings.colorMode = prefs.Get("Renderer.ColourMode", UINT32_C(0)); - ren->gravityFieldEnabled = prefs.Get("Renderer.GravityField", false); - ren->decorations_enable = prefs.Get("Renderer.Decorations", true); + rendererSettings.gravityFieldEnabled = prefs.Get("Renderer.GravityField", false); + rendererSettings.decorations_enable = prefs.Get("Renderer.Decorations", true); //Load config into simulation edgeMode = prefs.Get("Simulation.EdgeMode", NUM_EDGEMODES, EDGE_VOID); @@ -170,12 +170,12 @@ GameModel::~GameModel() { //Save to config: Prefs::DeferWrite dw(prefs); - prefs.Set("Renderer.ColourMode", ren->GetColorMode()); - prefs.Set("Renderer.DisplayMode", ren->GetDisplayMode()); - prefs.Set("Renderer.RenderMode", ren->GetRenderMode()); - prefs.Set("Renderer.GravityField", (bool)ren->gravityFieldEnabled); - prefs.Set("Renderer.Decorations", (bool)ren->decorations_enable); - prefs.Set("Renderer.DebugMode", ren->debugLines); //These two should always be equivalent, even though they are different things + prefs.Set("Renderer.ColourMode", rendererSettings.colorMode); + prefs.Set("Renderer.DisplayMode", rendererSettings.displayMode); + prefs.Set("Renderer.RenderMode", rendererSettings.renderMode); + prefs.Set("Renderer.GravityField", (bool)rendererSettings.gravityFieldEnabled); + prefs.Set("Renderer.Decorations", (bool)rendererSettings.decorations_enable); + prefs.Set("Renderer.DebugMode", rendererSettings.debugLines); //These two should always be equivalent, even though they are different things prefs.Set("Simulation.NewtonianGravity", bool(sim->grav)); prefs.Set("Simulation.AmbientHeat", sim->aheat_enable); prefs.Set("Simulation.PrettyPowder", sim->pretty_powder); @@ -1260,9 +1260,9 @@ bool GameModel::GetPaused() void GameModel::SetDecoration(bool decorationState) { - if (ren->decorations_enable != (decorationState?1:0)) + if (rendererSettings.decorations_enable != (decorationState?1:0)) { - ren->decorations_enable = decorationState?1:0; + rendererSettings.decorations_enable = decorationState?1:0; notifyDecorationChanged(); UpdateQuickOptions(); if (decorationState) @@ -1274,7 +1274,7 @@ void GameModel::SetDecoration(bool decorationState) bool GameModel::GetDecoration() { - return ren->decorations_enable?true:false; + return rendererSettings.decorations_enable?true:false; } void GameModel::SetAHeatEnable(bool aHeat) @@ -1318,7 +1318,7 @@ bool GameModel::GetNewtonianGrvity() void GameModel::ShowGravityGrid(bool showGrid) { - ren->gravityFieldEnabled = showGrid; + rendererSettings.gravityFieldEnabled = showGrid; if (showGrid) SetInfoTip("Gravity Grid: On"); else @@ -1327,7 +1327,7 @@ void GameModel::ShowGravityGrid(bool showGrid) bool GameModel::GetGravityGrid() { - return ren->gravityFieldEnabled; + return rendererSettings.gravityFieldEnabled; } void GameModel::FrameStep(int frames) diff --git a/src/gui/game/GameModel.h b/src/gui/game/GameModel.h index 2f2dcb0fb..444db9386 100644 --- a/src/gui/game/GameModel.h +++ b/src/gui/game/GameModel.h @@ -2,6 +2,7 @@ #include "gui/interface/Colour.h" #include "client/User.h" #include "gui/interface/Point.h" +#include "graphics/RendererSettings.h" #include #include #include @@ -66,6 +67,7 @@ private: Simulation * sim; Renderer * ren; + RendererSettings rendererSettings; std::vector menuList; std::vector quickOptions; int activeMenu; @@ -231,6 +233,10 @@ public: void SetUser(User user); Simulation * GetSimulation(); Renderer * GetRenderer(); + RendererSettings &GetRendererSettings() + { + return rendererSettings; + } void SetZoomEnabled(bool enabled); bool GetZoomEnabled(); void SetZoomSize(int size); diff --git a/src/gui/game/GameView.cpp b/src/gui/game/GameView.cpp index 375d40798..bf9535211 100644 --- a/src/gui/game/GameView.cpp +++ b/src/gui/game/GameView.cpp @@ -203,7 +203,6 @@ GameView::GameView(): recordingFolder(0), currentPoint(ui::Point(0, 0)), lastPoint(ui::Point(0, 0)), - ren(NULL), activeBrush(NULL), saveSimulationButtonEnabled(false), saveReuploadAllowed(true), @@ -332,6 +331,7 @@ GameView::GameView(): GameView::~GameView() { + StopRendererThread(); if(!colourPicker->GetParentWindow()) delete colourPicker; @@ -470,8 +470,7 @@ bool GameView::GetBrushEnable() void GameView::SetDebugHUD(bool mode) { showDebug = mode; - if (ren) - ren->debugLines = showDebug; + rendererSettings->debugLines = showDebug; } bool GameView::GetDebugHUD() @@ -518,9 +517,10 @@ void GameView::NotifyActiveToolsChanged(GameModel * sender) decoBrush = sender->GetActiveTool(0)->Identifier.BeginsWith("DEFAULT_DECOR_"); - if (sender->GetRenderer()->findingElement) + auto &settings = sender->GetRendererSettings(); + if (settings.findingElement) { - ren->findingElement = FindingElementCandidate(); + settings.findingElement = FindingElementCandidate(); } } @@ -734,6 +734,7 @@ void GameView::NotifyColourSelectorColourChanged(GameModel * sender) void GameView::NotifyRendererChanged(GameModel * sender) { ren = sender->GetRenderer(); + rendererSettings = &sender->GetRendererSettings(); } void GameView::NotifySimulationChanged(GameModel * sender) @@ -1443,13 +1444,13 @@ void GameView::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, if (ctrl) { auto findingElementCandidate = FindingElementCandidate(); - if (ren->findingElement == findingElementCandidate) + if (rendererSettings->findingElement == findingElementCandidate) { - ren->findingElement = std::nullopt; + rendererSettings->findingElement = std::nullopt; } else { - ren->findingElement = findingElementCandidate; + rendererSettings->findingElement = findingElementCandidate; } } else @@ -1982,7 +1983,7 @@ void GameView::NotifyTransformedPlaceSaveChanged(GameModel *sender) { if (sender->GetTransformedPlaceSave()) { - placeSaveThumb = SaveRenderer::Ref().Render(sender->GetTransformedPlaceSave(), true, true, sender->GetRenderer()); + placeSaveThumb = SaveRenderer::Ref().Render(sender->GetTransformedPlaceSave(), true, true, sender->GetRendererSettings()); selectMode = PlaceSave; selectPoint2 = mousePosition; } @@ -2126,166 +2127,189 @@ void GameView::SetSaveButtonTooltips() saveSimulationButton->SetToolTips("Re-upload the current simulation", "Upload a new simulation. Hold Ctrl to save offline."); } +void GameView::RenderSimulation(const RenderableSimulation &sim, bool handleEvents) +{ + ren->sim = ∼ + ren->clearScreen(); + ren->draw_air(); + if (handleEvents) + { + c->BeforeSimDraw(); + } + { + // we may write graphicscache here + auto &sd = SimulationData::Ref(); + std::unique_lock lk(sd.elementGraphicsMx); + ren->RenderSimulation(); + } + if (handleEvents) + { + c->AfterSimDraw(); + } + ren->sim = nullptr; +} + void GameView::OnDraw() { Graphics * g = GetGraphics(); - if (ren) + + auto wantRendererThread = !c->HaveSimGraphicsEventHandlers(); + if (wantRendererThread) { - // we're the main thread, we may write graphicscache - auto &sd = SimulationData::Ref(); - std::unique_lock lk(sd.elementGraphicsMx); - ren->sim = sim; - ren->clearScreen(); - ren->draw_air(); - c->BeforeSimDraw(); - ren->RenderSimulation(); - ren->sim = nullptr; - - c->AfterSimDraw(); - + StartRendererThread(); + WaitForRendererThread(); rendererFrame = ren->GetVideo(); - std::copy_n(rendererFrame.data(), rendererFrame.Size().X * rendererFrame.Size().Y, g->Data()); + DispatchRendererThread(); + } + else + { + PauseRendererThread(); + ren->ApplySettings(*rendererSettings); + RenderSimulation(*sim, true); + rendererFrame = ren->GetVideo(); + } - if (showBrush && selectMode == SelectNone && (!zoomEnabled || zoomCursorFixed) && activeBrush && (isMouseDown || (currentMouse.X >= 0 && currentMouse.X < XRES && currentMouse.Y >= 0 && currentMouse.Y < YRES))) + std::copy_n(rendererFrame.data(), rendererFrame.Size().X * rendererFrame.Size().Y, g->Data()); + + if (showBrush && selectMode == SelectNone && (!zoomEnabled || zoomCursorFixed) && activeBrush && (isMouseDown || (currentMouse.X >= 0 && currentMouse.X < XRES && currentMouse.Y >= 0 && currentMouse.Y < YRES))) + { + ui::Point finalCurrentMouse = windTool ? c->PointTranslateNoClamp(currentMouse) : c->PointTranslate(currentMouse); + ui::Point initialDrawPoint = drawPoint1; + + if (wallBrush) { - ui::Point finalCurrentMouse = windTool ? c->PointTranslateNoClamp(currentMouse) : c->PointTranslate(currentMouse); - ui::Point initialDrawPoint = drawPoint1; - - if (wallBrush) - { - finalCurrentMouse = c->NormaliseBlockCoord(finalCurrentMouse); - initialDrawPoint = c->NormaliseBlockCoord(initialDrawPoint); - } - - if (drawMode == DrawRect && isMouseDown) - { - if (drawSnap) - { - finalCurrentMouse = rectSnapCoords(c->PointTranslate(initialDrawPoint), finalCurrentMouse); - } - if (wallBrush) - { - if (finalCurrentMouse.X > initialDrawPoint.X) - finalCurrentMouse.X += CELL-1; - else - initialDrawPoint.X += CELL-1; - - if (finalCurrentMouse.Y > initialDrawPoint.Y) - finalCurrentMouse.Y += CELL-1; - else - initialDrawPoint.Y += CELL-1; - } - activeBrush->RenderRect(g, c->PointTranslate(initialDrawPoint), finalCurrentMouse); - } - else if (drawMode == DrawLine && isMouseDown) - { - if (drawSnap) - { - finalCurrentMouse = lineSnapCoords(c->PointTranslate(initialDrawPoint), finalCurrentMouse); - } - activeBrush->RenderLine(g, c->PointTranslate(initialDrawPoint), finalCurrentMouse); - } - else if (drawMode == DrawFill)// || altBehaviour) - { - if (!decoBrush) - activeBrush->RenderFill(g, finalCurrentMouse); - } - if (drawMode == DrawPoints || drawMode==DrawLine || (drawMode == DrawRect && !isMouseDown)) - { - if (wallBrush) - { - ui::Point finalBrushRadius = c->NormaliseBlockCoord(activeBrush->GetRadius()); - auto topLeft = finalCurrentMouse - finalBrushRadius; - auto bottomRight = finalCurrentMouse + finalBrushRadius + Vec2{ CELL - 1, CELL - 1 }; - g->XorLine({ topLeft.X, topLeft.Y }, { bottomRight.X, topLeft.Y }); - g->XorLine({ topLeft.X, bottomRight.Y }, { bottomRight.X, bottomRight.Y }); - g->XorLine({ topLeft.X, topLeft.Y + 1 }, { topLeft.X, bottomRight.Y - 1 }); // offset by 1 so the corners don't get xor'd twice - g->XorLine({ bottomRight.X, topLeft.Y + 1 }, { bottomRight.X, bottomRight.Y - 1 }); // offset by 1 so the corners don't get xor'd twice - } - else - { - activeBrush->RenderPoint(g, finalCurrentMouse); - } - } + finalCurrentMouse = c->NormaliseBlockCoord(finalCurrentMouse); + initialDrawPoint = c->NormaliseBlockCoord(initialDrawPoint); } - if(selectMode!=SelectNone) + if (drawMode == DrawRect && isMouseDown) { - if(selectMode==PlaceSave) + if (drawSnap) { - if(placeSaveThumb && selectPoint2.X!=-1) - { - auto rect = RectSized(PlaceSavePos() * CELL, placeSaveThumb->Size()); - g->BlendImage(placeSaveThumb->Data(), 0x80, rect); - g->XorDottedRect(rect); - } + finalCurrentMouse = rectSnapCoords(c->PointTranslate(initialDrawPoint), finalCurrentMouse); + } + if (wallBrush) + { + if (finalCurrentMouse.X > initialDrawPoint.X) + finalCurrentMouse.X += CELL-1; + else + initialDrawPoint.X += CELL-1; + + if (finalCurrentMouse.Y > initialDrawPoint.Y) + finalCurrentMouse.Y += CELL-1; + else + initialDrawPoint.Y += CELL-1; + } + activeBrush->RenderRect(g, c->PointTranslate(initialDrawPoint), finalCurrentMouse); + } + else if (drawMode == DrawLine && isMouseDown) + { + if (drawSnap) + { + finalCurrentMouse = lineSnapCoords(c->PointTranslate(initialDrawPoint), finalCurrentMouse); + } + activeBrush->RenderLine(g, c->PointTranslate(initialDrawPoint), finalCurrentMouse); + } + else if (drawMode == DrawFill)// || altBehaviour) + { + if (!decoBrush) + activeBrush->RenderFill(g, finalCurrentMouse); + } + if (drawMode == DrawPoints || drawMode==DrawLine || (drawMode == DrawRect && !isMouseDown)) + { + if (wallBrush) + { + ui::Point finalBrushRadius = c->NormaliseBlockCoord(activeBrush->GetRadius()); + auto topLeft = finalCurrentMouse - finalBrushRadius; + auto bottomRight = finalCurrentMouse + finalBrushRadius + Vec2{ CELL - 1, CELL - 1 }; + g->XorLine({ topLeft.X, topLeft.Y }, { bottomRight.X, topLeft.Y }); + g->XorLine({ topLeft.X, bottomRight.Y }, { bottomRight.X, bottomRight.Y }); + g->XorLine({ topLeft.X, topLeft.Y + 1 }, { topLeft.X, bottomRight.Y - 1 }); // offset by 1 so the corners don't get xor'd twice + g->XorLine({ bottomRight.X, topLeft.Y + 1 }, { bottomRight.X, bottomRight.Y - 1 }); // offset by 1 so the corners don't get xor'd twice } else { - if(selectPoint1.X==-1) - { - g->BlendFilledRect(RectSized(Vec2{ 0, 0 }, Vec2{ XRES, YRES }), 0x000000_rgb .WithAlpha(100)); - } - else - { - int x2 = (selectPoint1.X>selectPoint2.X)?selectPoint1.X:selectPoint2.X; - int y2 = (selectPoint1.Y>selectPoint2.Y)?selectPoint1.Y:selectPoint2.Y; - int x1 = (selectPoint2.XXRES-1) - x2 = XRES-1; - if(y2>YRES-1) - y2 = YRES-1; - - g->BlendFilledRect(RectSized(Vec2{ 0, 0 }, Vec2{ XRES, y1 }), 0x000000_rgb .WithAlpha(100)); - g->BlendFilledRect(RectSized(Vec2{ 0, y2+1 }, Vec2{ XRES, YRES-y2-1 }), 0x000000_rgb .WithAlpha(100)); - - g->BlendFilledRect(RectSized(Vec2{ 0, y1 }, Vec2{ x1, (y2-y1)+1 }), 0x000000_rgb .WithAlpha(100)); - g->BlendFilledRect(RectSized(Vec2{ x2+1, y1 }, Vec2{ XRES-x2-1, (y2-y1)+1 }), 0x000000_rgb .WithAlpha(100)); - - g->XorDottedRect(RectBetween(Vec2{ x1, y1 }, Vec2{ x2, y2 })); - } + activeBrush->RenderPoint(g, finalCurrentMouse); } } + } - g->RenderZoom(); - - if (doScreenshot) + if(selectMode!=SelectNone) + { + if(selectMode==PlaceSave) { - doScreenshot = false; - TakeScreenshot(0, 0); - } - - if(recording) - { - std::vector data = VideoBuffer(rendererFrame).ToPPM(); - - ByteString filename = ByteString::Build("recordings", PATH_SEP_CHAR, recordingFolder, PATH_SEP_CHAR, "frame_", Format::Width(recordingIndex++, 6), ".ppm"); - - Platform::WriteFile(data, filename); - } - - if (logEntries.size()) - { - int startX = 20; - int startY = YRES-20; - std::deque >::iterator iter; - for(iter = logEntries.begin(); iter != logEntries.end(); iter++) + if(placeSaveThumb && selectPoint2.X!=-1) { - String message = (*iter).first; - int alpha = std::min((*iter).second, 255); - if (alpha <= 0) //erase this and everything older - { - logEntries.erase(iter, logEntries.end()); - break; - } - startY -= 14; - g->BlendFilledRect(RectSized(Vec2{ startX-3, startY-3 }, Vec2{ Graphics::TextSize(message).X + 5, 14 }), 0x000000_rgb .WithAlpha(std::min(100, alpha))); - g->BlendText({ startX, startY }, message, 0xFFFFFF_rgb .WithAlpha(alpha)); - (*iter).second -= 3; + auto rect = RectSized(PlaceSavePos() * CELL, placeSaveThumb->Size()); + g->BlendImage(placeSaveThumb->Data(), 0x80, rect); + g->XorDottedRect(rect); } } + else + { + if(selectPoint1.X==-1) + { + g->BlendFilledRect(RectSized(Vec2{ 0, 0 }, Vec2{ XRES, YRES }), 0x000000_rgb .WithAlpha(100)); + } + else + { + int x2 = (selectPoint1.X>selectPoint2.X)?selectPoint1.X:selectPoint2.X; + int y2 = (selectPoint1.Y>selectPoint2.Y)?selectPoint1.Y:selectPoint2.Y; + int x1 = (selectPoint2.XXRES-1) + x2 = XRES-1; + if(y2>YRES-1) + y2 = YRES-1; + + g->BlendFilledRect(RectSized(Vec2{ 0, 0 }, Vec2{ XRES, y1 }), 0x000000_rgb .WithAlpha(100)); + g->BlendFilledRect(RectSized(Vec2{ 0, y2+1 }, Vec2{ XRES, YRES-y2-1 }), 0x000000_rgb .WithAlpha(100)); + + g->BlendFilledRect(RectSized(Vec2{ 0, y1 }, Vec2{ x1, (y2-y1)+1 }), 0x000000_rgb .WithAlpha(100)); + g->BlendFilledRect(RectSized(Vec2{ x2+1, y1 }, Vec2{ XRES-x2-1, (y2-y1)+1 }), 0x000000_rgb .WithAlpha(100)); + + g->XorDottedRect(RectBetween(Vec2{ x1, y1 }, Vec2{ x2, y2 })); + } + } + } + + g->RenderZoom(); + + if (doScreenshot) + { + doScreenshot = false; + TakeScreenshot(0, 0); + } + + if(recording) + { + std::vector data = VideoBuffer(rendererFrame).ToPPM(); + + ByteString filename = ByteString::Build("recordings", PATH_SEP_CHAR, recordingFolder, PATH_SEP_CHAR, "frame_", Format::Width(recordingIndex++, 6), ".ppm"); + + Platform::WriteFile(data, filename); + } + + if (logEntries.size()) + { + int startX = 20; + int startY = YRES-20; + std::deque >::iterator iter; + for(iter = logEntries.begin(); iter != logEntries.end(); iter++) + { + String message = (*iter).first; + int alpha = std::min((*iter).second, 255); + if (alpha <= 0) //erase this and everything older + { + logEntries.erase(iter, logEntries.end()); + break; + } + startY -= 14; + g->BlendFilledRect(RectSized(Vec2{ startX-3, startY-3 }, Vec2{ Graphics::TextSize(message).X + 5, 14 }), 0x000000_rgb .WithAlpha(std::min(100, alpha))); + g->BlendText({ startX, startY }, message, 0xFFFFFF_rgb .WithAlpha(alpha)); + (*iter).second -= 3; + } } if (recording) @@ -2487,8 +2511,8 @@ void GameView::OnDraw() if (showDebug) { - if (ren->findingElement) - fpsInfo << " Parts: " << ren->foundElements << "/" << sample.NumParts; + if (rendererSettings->findingElement) + fpsInfo << " Parts: " << rendererSettings->foundElements << "/" << sample.NumParts; else fpsInfo << " Parts: " << sample.NumParts; } @@ -2496,10 +2520,17 @@ void GameView::OnDraw() fpsInfo << " [REPLACE MODE]"; if (c->GetReplaceModeFlags()&SPECIFIC_DELETE) fpsInfo << " [SPECIFIC DELETE]"; - if (ren && ren->GetGridSize()) - fpsInfo << " [GRID: " << ren->GetGridSize() << "]"; - if (ren && ren->findingElement) + if (rendererSettings->gridSize) + fpsInfo << " [GRID: " << rendererSettings->gridSize << "]"; + if (rendererSettings->findingElement) fpsInfo << " [FIND]"; + if (showDebug) + { + if (wantRendererThread) + { + fpsInfo << " [SRT]"; + } + } int textWidth = Graphics::TextSize(fpsInfo.Build()).X - 1; int alpha = 255-introText*5; @@ -2584,3 +2615,106 @@ pixel GameView::GetPixelUnderMouse() const } return rendererFrame[point]; } + +void GameView::RendererThread() +{ + while (true) + { + { + std::unique_lock lk(rendererThreadMx); + rendererThreadOwnsRenderer = false; + rendererThreadCv.notify_one(); + rendererThreadCv.wait(lk, [this]() { + return rendererThreadState == rendererThreadStopping || rendererThreadOwnsRenderer; + }); + if (rendererThreadState == rendererThreadStopping) + { + break; + } + } + RenderSimulation(*rendererThreadSim, false); + } +} + +void GameView::StartRendererThread() +{ + bool start = false; + bool notify = false; + { + std::lock_guard lk(rendererThreadMx); + if (rendererThreadState == rendererThreadAbsent) + { + rendererThreadSim = std::make_unique(); + rendererThreadState = rendererThreadRunning; + start = true; + } + else if (rendererThreadState == rendererThreadPaused) + { + rendererThreadState = rendererThreadRunning; + notify = true; + } + } + if (start) + { + rendererThread = std::thread([this]() { + RendererThread(); + }); + notify = true; + } + if (notify) + { + DispatchRendererThread(); + } +} + +void GameView::StopRendererThread() +{ + bool join = false; + { + std::lock_guard lk(rendererThreadMx); + if (rendererThreadState != rendererThreadAbsent) + { + rendererThreadState = rendererThreadStopping; + join = true; + } + } + if (join) + { + rendererThreadCv.notify_one(); + rendererThread.join(); + } +} + +void GameView::PauseRendererThread() +{ + std::unique_lock lk(rendererThreadMx); + if (rendererThreadState == rendererThreadRunning) + { + rendererThreadState = rendererThreadPaused; + rendererThreadCv.notify_one(); + rendererThreadCv.wait(lk, [this]() { + return !rendererThreadOwnsRenderer; + }); + } +} + +void GameView::DispatchRendererThread() +{ + ren->ApplySettings(*rendererSettings); + *rendererThreadSim = *sim; + rendererThreadSim->useLuaCallbacks = false; + rendererThreadOwnsRenderer = true; + { + std::lock_guard lk(rendererThreadMx); + rendererThreadOwnsRenderer = true; + } + rendererThreadCv.notify_one(); +} + +void GameView::WaitForRendererThread() +{ + std::unique_lock lk(rendererThreadMx); + rendererThreadCv.wait(lk, [this]() { + return !rendererThreadOwnsRenderer; + }); +} diff --git a/src/gui/game/GameView.h b/src/gui/game/GameView.h index 3e55ec1d4..3fe832a54 100644 --- a/src/gui/game/GameView.h +++ b/src/gui/game/GameView.h @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include enum DrawMode { @@ -29,9 +32,11 @@ namespace ui class SplitButton; class Simulation; +struct RenderableSimulation; class MenuButton; class Renderer; +struct RendererSettings; class VideoBuffer; class ToolButton; class GameController; @@ -82,7 +87,8 @@ private: ui::Point currentPoint, lastPoint; GameController * c; - Renderer * ren; + Renderer *ren = nullptr; + RendererSettings *rendererSettings = nullptr; Simulation *sim = nullptr; Brush const *activeBrush; //UI Elements @@ -147,10 +153,28 @@ private: Vec2 PlaceSavePos() const; std::optional FindingElementCandidate() const; + enum RendererThreadState + { + rendererThreadAbsent, + rendererThreadRunning, + rendererThreadPaused, + rendererThreadStopping, + }; + RendererThreadState rendererThreadState = rendererThreadAbsent; + std::thread rendererThread; + std::mutex rendererThreadMx; + std::condition_variable rendererThreadCv; + bool rendererThreadOwnsRenderer = false; + void StartRendererThread(); + void StopRendererThread(); + void RendererThread(); + void WaitForRendererThread(); + void DispatchRendererThread(); + std::unique_ptr rendererThreadSim; public: GameView(); - virtual ~GameView(); + ~GameView(); //Breaks MVC, but any other way is going to be more of a mess. ui::Point GetMousePosition(); @@ -238,4 +262,12 @@ public: pixel GetPixelUnderMouse() const; RendererFrame rendererFrame; + // Call this before accessing Renderer "out of turn", e.g. from RenderView. This *does not* + // include OptionsModel or Lua setting functions because they only access the RendererSettings + // in GameModel, or Lua drawing functions because they only access Renderer in eventTraitSimGraphics + // and *SimDraw events, and the renderer thread gets paused anyway if there are handlers + // installed for such events. + void PauseRendererThread(); + + void RenderSimulation(const RenderableSimulation &sim, bool handleEvents); }; diff --git a/src/gui/preview/PreviewView.cpp b/src/gui/preview/PreviewView.cpp index 938feaad1..d29bc5634 100644 --- a/src/gui/preview/PreviewView.cpp +++ b/src/gui/preview/PreviewView.cpp @@ -575,7 +575,7 @@ void PreviewView::NotifySaveChanged(PreviewModel * sender) if(save->GetGameSave()) { missingElements = save->GetGameSave()->missingElements; - savePreview = SaveRenderer::Ref().Render(save->GetGameSave(), false, true); + savePreview = SaveRenderer::Ref().Render(save->GetGameSave(), false, true, RendererSettings{}); if (savePreview) savePreview->ResizeToFit(RES / 2, true); missingElementsButton->Visible = missingElements; diff --git a/src/gui/render/RenderController.cpp b/src/gui/render/RenderController.cpp index 07de224d9..9c371a275 100644 --- a/src/gui/render/RenderController.cpp +++ b/src/gui/render/RenderController.cpp @@ -5,7 +5,7 @@ #include "Controller.h" -RenderController::RenderController(Simulation *sim, Renderer * ren, std::function onDone_): +RenderController::RenderController(Simulation *sim, Renderer * ren, RendererSettings *rendererSettings, std::function onDone_): HasExited(false) { renderView = new RenderView(); @@ -14,7 +14,7 @@ RenderController::RenderController(Simulation *sim, Renderer * ren, std::functio renderView->AttachController(this); renderModel->AddObserver(renderView); - renderModel->SetRenderer(ren); + renderModel->SetRenderer(ren, rendererSettings); renderModel->SetSimulation(sim); onDone = onDone_; } diff --git a/src/gui/render/RenderController.h b/src/gui/render/RenderController.h index b027d9125..82be481b3 100644 --- a/src/gui/render/RenderController.h +++ b/src/gui/render/RenderController.h @@ -5,6 +5,7 @@ class RenderView; class RenderModel; class Renderer; +struct RendererSettings; class Simulation; class RenderController { @@ -13,7 +14,7 @@ class RenderController std::function onDone; public: bool HasExited; - RenderController(Simulation *sim, Renderer * ren, std::function onDone = nullptr); + RenderController(Simulation *sim, Renderer * ren, RendererSettings *rendererSettings, std::function onDone = nullptr); void Exit(); RenderView * GetView() { return renderView; } virtual ~RenderController(); diff --git a/src/gui/render/RenderModel.cpp b/src/gui/render/RenderModel.cpp index c15fc1611..1f4ef5064 100644 --- a/src/gui/render/RenderModel.cpp +++ b/src/gui/render/RenderModel.cpp @@ -1,17 +1,10 @@ #include "RenderModel.h" - #include "RenderView.h" - #include "gui/game/RenderPreset.h" - +#include "gui/game/GameController.h" +#include "gui/game/GameView.h" #include "graphics/Renderer.h" -RenderModel::RenderModel(): - renderer(NULL) -{ - -} - void RenderModel::AddObserver(RenderView * observer) { observers.push_back(observer); @@ -23,44 +16,35 @@ void RenderModel::AddObserver(RenderView * observer) void RenderModel::SetRenderMode(uint32_t newRenderMode) { - if (renderer) - { - renderer->SetRenderMode(newRenderMode); - } + rendererSettings->renderMode = newRenderMode; notifyRenderChanged(); } uint32_t RenderModel::GetRenderMode() { - return renderer ? renderer->GetRenderMode() : 0; + return rendererSettings->renderMode; } void RenderModel::SetDisplayMode(uint32_t newDisplayMode) { - if (renderer) - { - renderer->SetDisplayMode(newDisplayMode); - } + rendererSettings->displayMode = newDisplayMode; notifyDisplayChanged(); } uint32_t RenderModel::GetDisplayMode() { - return renderer ? renderer->GetDisplayMode() : 0; + return rendererSettings->displayMode; } void RenderModel::SetColorMode(uint32_t newColorMode) { - if (renderer) - { - renderer->SetColorMode(newColorMode); - } + rendererSettings->colorMode = newColorMode; notifyColourChanged(); } uint32_t RenderModel::GetColorMode() { - return renderer ? renderer->GetColorMode() : 0; + return rendererSettings->colorMode; } void RenderModel::LoadRenderPreset(int presetNum) @@ -71,9 +55,10 @@ void RenderModel::LoadRenderPreset(int presetNum) SetColorMode(preset.colorMode); } -void RenderModel::SetRenderer(Renderer * ren) +void RenderModel::SetRenderer(Renderer * ren, RendererSettings *newRendererSettings) { renderer = ren; + rendererSettings = newRendererSettings; notifyRendererChanged(); notifyRenderChanged(); notifyDisplayChanged(); @@ -94,6 +79,11 @@ Renderer * RenderModel::GetRenderer() return renderer; } +RendererSettings *RenderModel::GetRendererSettings() +{ + return rendererSettings; +} + Simulation *RenderModel::GetSimulation() { return sim; diff --git a/src/gui/render/RenderModel.h b/src/gui/render/RenderModel.h index 448d3db37..8f33d9bb8 100644 --- a/src/gui/render/RenderModel.h +++ b/src/gui/render/RenderModel.h @@ -4,11 +4,13 @@ class RenderView; class Renderer; +struct RendererSettings; class Simulation; class RenderModel { std::vector observers; - Renderer * renderer; + Renderer * renderer = nullptr; + RendererSettings *rendererSettings = nullptr; Simulation *sim = nullptr; void notifyRendererChanged(); void notifySimulationChanged(); @@ -16,11 +18,11 @@ class RenderModel void notifyDisplayChanged(); void notifyColourChanged(); public: - RenderModel(); Renderer * GetRenderer(); + RendererSettings *GetRendererSettings(); Simulation *GetSimulation(); void AddObserver(RenderView * observer); - void SetRenderer(Renderer * ren); + void SetRenderer(Renderer * ren, RendererSettings *newRendererSettings); void SetSimulation(Simulation *newSim); void SetRenderMode(uint32_t newRenderMode); uint32_t GetRenderMode(); diff --git a/src/gui/render/RenderView.cpp b/src/gui/render/RenderView.cpp index f900e2b47..4e8a0f989 100644 --- a/src/gui/render/RenderView.cpp +++ b/src/gui/render/RenderView.cpp @@ -1,18 +1,16 @@ #include "RenderView.h" - #include "simulation/ElementGraphics.h" #include "simulation/SimulationData.h" #include "simulation/Simulation.h" - #include "graphics/Graphics.h" #include "graphics/Renderer.h" #include "graphics/VideoBuffer.h" - #include "RenderController.h" #include "RenderModel.h" - #include "gui/interface/Checkbox.h" #include "gui/interface/Button.h" +#include "gui/game/GameController.h" +#include "gui/game/GameView.h" class ModeCheckbox : public ui::Checkbox { @@ -144,6 +142,7 @@ void RenderView::OnTryExit(ExitMethod method) void RenderView::NotifyRendererChanged(RenderModel * sender) { ren = sender->GetRenderer(); + rendererSettings = sender->GetRendererSettings(); } void RenderView::NotifySimulationChanged(RenderModel * sender) @@ -183,20 +182,14 @@ void RenderView::OnDraw() { Graphics * g = GetGraphics(); g->DrawFilledRect(WINDOW.OriginRect(), 0x000000_rgb); - if(ren) + auto *view = GameController::Ref().GetView(); + view->PauseRendererThread(); + ren->ApplySettings(*rendererSettings); + view->RenderSimulation(*sim, true); + for (auto y = 0; y < YRES; ++y) { - // we're the main thread, we may write graphicscache - auto &sd = SimulationData::Ref(); - std::unique_lock lk(sd.elementGraphicsMx); - ren->sim = sim; - ren->clearScreen(); - ren->RenderSimulation(); - ren->sim = nullptr; - for (auto y = 0; y < YRES; ++y) - { - auto &video = ren->GetVideo(); - std::copy_n(video.data() + video.Size().X * y, video.Size().X, g->Data() + g->Size().X * y); - } + auto &video = ren->GetVideo(); + std::copy_n(video.data() + video.Size().X * y, video.Size().X, g->Data() + g->Size().X * y); } g->DrawLine({ 0, YRES }, { XRES-1, YRES }, 0xC8C8C8_rgb); g->DrawLine({ line1, YRES }, { line1, WINDOWH }, 0xC8C8C8_rgb); diff --git a/src/gui/render/RenderView.h b/src/gui/render/RenderView.h index 2de166324..d6ac49768 100644 --- a/src/gui/render/RenderView.h +++ b/src/gui/render/RenderView.h @@ -5,12 +5,14 @@ class ModeCheckbox; class Renderer; +struct RendererSettings; class Simulation; class RenderController; class RenderModel; class RenderView: public ui::Window { RenderController * c; Renderer * ren; + RendererSettings *rendererSettings = nullptr; Simulation *sim = nullptr; std::vector renderModes; std::vector displayModes; diff --git a/src/lua/CommandInterface.h b/src/lua/CommandInterface.h index 5cca385c0..0afa3ad50 100644 --- a/src/lua/CommandInterface.h +++ b/src/lua/CommandInterface.h @@ -34,6 +34,7 @@ public: void Init(); bool HandleEvent(const GameControllerEvent &event); + bool HaveSimGraphicsEventHandlers(); int Command(String command); String FormatCommand(String command); diff --git a/src/lua/LuaGraphics.cpp b/src/lua/LuaGraphics.cpp index ba0ae5d56..f6c746824 100644 --- a/src/lua/LuaGraphics.cpp +++ b/src/lua/LuaGraphics.cpp @@ -11,16 +11,6 @@ static int32_t int32Truncate(double n) return int32_t(n); } -static std::variant currentGraphics() -{ - auto *lsi = GetLSI(); - if (lsi->eventTraits & eventTraitSimGraphics) - { - return lsi->ren; - } - return lsi->g; -} - static int textSize(lua_State *L) { auto text = tpt_lua_optString(L, 1, ""); @@ -51,7 +41,7 @@ static int drawText(lua_State *L) std::visit([x, y, r, g, b, a, &text](auto p) { p->BlendText({ x, y }, text, RGBA(r, g, b, a)); - }, currentGraphics()); + }, GetLSI()->GetGraphics()); return 0; } @@ -73,7 +63,7 @@ static int drawPixel(lua_State *L) else if (a > 255) a = 255; std::visit([x, y, r, g, b, a](auto p) { p->BlendPixel({ x, y }, RGBA(r, g, b, a)); - }, currentGraphics()); + }, GetLSI()->GetGraphics()); return 0; } @@ -106,7 +96,7 @@ static int drawLine(lua_State *L) { p->BlendLine({ x1, y1 }, { x2, y2 }, RGBA(r, g, b, a)); } - }, currentGraphics()); + }, GetLSI()->GetGraphics()); return 0; } @@ -139,7 +129,7 @@ static int drawRect(lua_State *L) { p->BlendRect(RectSized(Vec2{ x, y }, Vec2{ width, height }), RGBA(r, g, b, a)); } - }, currentGraphics()); + }, GetLSI()->GetGraphics()); return 0; } @@ -172,7 +162,7 @@ static int fillRect(lua_State *L) { p->BlendFilledRect(RectSized(Vec2{ x, y }, Vec2{ width, height }), RGBA(r, g, b, a)); } - }, currentGraphics()); + }, GetLSI()->GetGraphics()); return 0; } @@ -198,7 +188,7 @@ static int drawCircle(lua_State *L) std::visit([x, y, rx, ry, r, g, b, a](auto p) { p->BlendEllipse({ x, y }, { abs(rx), abs(ry) }, RGBA(r, g, b, a)); - }, currentGraphics()); + }, GetLSI()->GetGraphics()); return 0; } @@ -224,7 +214,7 @@ static int fillCircle(lua_State *L) std::visit([x, y, rx, ry, r, g, b, a](auto p) { p->BlendFilledEllipse({ x, y }, { abs(rx), abs(ry) }, RGBA(r, g, b, a)); - }, currentGraphics()); + }, GetLSI()->GetGraphics()); return 0; } diff --git a/src/lua/LuaRenderer.cpp b/src/lua/LuaRenderer.cpp index 5674c81d6..791abac57 100644 --- a/src/lua/LuaRenderer.cpp +++ b/src/lua/LuaRenderer.cpp @@ -10,10 +10,10 @@ static int renderMode(lua_State *L) auto *lsi = GetLSI(); if (lua_gettop(L)) { - lsi->ren->SetRenderMode(luaL_checkinteger(L, 1)); + lsi->gameModel->GetRendererSettings().renderMode = luaL_checkinteger(L, 1); return 0; } - lua_pushinteger(L, lsi->ren->GetRenderMode()); + lua_pushinteger(L, lsi->gameModel->GetRendererSettings().renderMode); return 1; } @@ -63,11 +63,10 @@ static int fireSize(lua_State *L) auto *lsi = GetLSI(); if (lua_gettop(L) < 1) { - lua_pushnumber(L, lsi->gameModel->GetRenderer()->GetFireIntensity()); + lua_pushnumber(L, lsi->gameModel->GetRendererSettings().fireIntensity); return 1; } - float fireintensity = float(luaL_checknumber(L, 1)); - lsi->gameModel->GetRenderer()->prepare_alpha(CELL, fireintensity); + lsi->gameModel->GetRendererSettings().fireIntensity = float(luaL_checknumber(L, 1)); return 0; } @@ -76,10 +75,10 @@ static int displayMode(lua_State *L) auto *lsi = GetLSI(); if (lua_gettop(L)) { - lsi->ren->SetDisplayMode(luaL_checkinteger(L, 1)); + lsi->gameModel->GetRendererSettings().displayMode = luaL_checkinteger(L, 1); return 0; } - lua_pushinteger(L, lsi->ren->GetDisplayMode()); + lua_pushinteger(L, lsi->gameModel->GetRendererSettings().displayMode); return 1; } @@ -89,10 +88,10 @@ static int colorMode(lua_State *L) auto *lsi = GetLSI(); if (lua_gettop(L)) { - lsi->ren->SetColorMode(luaL_checkinteger(L, 1)); + lsi->gameModel->GetRendererSettings().colorMode = luaL_checkinteger(L, 1); return 0; } - lua_pushinteger(L, lsi->ren->GetColorMode()); + lua_pushinteger(L, lsi->gameModel->GetRendererSettings().colorMode); return 1; } @@ -117,11 +116,11 @@ static int grid(lua_State *L) int acount = lua_gettop(L); if (acount == 0) { - lua_pushnumber(L, lsi->ren->GetGridSize()); + lua_pushnumber(L, lsi->gameModel->GetRendererSettings().gridSize); return 1; } int grid = luaL_optint(L, 1, -1); - lsi->ren->SetGridSize(grid); + lsi->gameModel->GetRendererSettings().gridSize = grid; return 0; } diff --git a/src/lua/LuaScriptInterface.cpp b/src/lua/LuaScriptInterface.cpp index 6f37a8d58..37ae26776 100644 --- a/src/lua/LuaScriptInterface.cpp +++ b/src/lua/LuaScriptInterface.cpp @@ -116,12 +116,12 @@ String LuaGetError() LuaScriptInterface::LuaScriptInterface(GameController *newGameController, GameModel *newGameModel) : CommandInterface(newGameController, newGameModel), + ren(newGameModel->GetRenderer()), gameModel(newGameModel), gameController(newGameController), window(gameController->GetView()), sim(gameModel->GetSimulation()), g(ui::Engine::Ref().g), - ren(gameModel->GetRenderer()), customElements(PT_NUM), gameControllerEventHandlers(std::variant_size_v) { @@ -417,6 +417,42 @@ bool CommandInterface::HandleEvent(const GameControllerEvent &event) return cont; } +template +std::enable_if_t, bool> HaveSimGraphicsEventHandlersHelper(lua_State *L, std::vector &gameControllerEventHandlers) +{ + if (std::variant_alternative_t::traits & eventTraitSimGraphics) + { + gameControllerEventHandlers[Index].Push(L); + auto have = lua_objlen(L, -1) > 0; + lua_pop(L, 1); + if (have) + { + return true; + } + } + return HaveSimGraphicsEventHandlersHelper(L, gameControllerEventHandlers); +} + +template +std::enable_if_t, bool> HaveSimGraphicsEventHandlersHelper(lua_State *L, std::vector &gameControllerEventHandlers) +{ + return false; +} + +bool CommandInterface::HaveSimGraphicsEventHandlers() +{ + auto &sd = SimulationData::CRef(); + auto *lsi = static_cast(this); + for (int i = 0; i < int(lsi->customElements.size()); ++i) + { + if (lsi->customElements[i].graphics && !sd.graphicscache[i].isready && lsi->sim->elementCount[i]) + { + return true; + } + } + return HaveSimGraphicsEventHandlersHelper<0>(lsi->L, lsi->gameControllerEventHandlers); +} + void CommandInterface::OnTick() { auto *lsi = static_cast(this); @@ -804,3 +840,4 @@ void CommandInterfaceDeleter::operator ()(CommandInterface *ptr) const { delete static_cast(ptr); } + diff --git a/src/lua/LuaScriptInterface.h b/src/lua/LuaScriptInterface.h index 6b0ee07d1..e6f58fdc0 100644 --- a/src/lua/LuaScriptInterface.h +++ b/src/lua/LuaScriptInterface.h @@ -62,6 +62,8 @@ class LuaScriptInterface : public CommandInterface { LuaStatePtr luaState; + Renderer *ren; + public: lua_State *L{}; @@ -70,7 +72,18 @@ public: ui::Window *window; Simulation *sim; Graphics *g; - Renderer *ren; + + std::variant GetGraphics() + { + if (eventTraits & eventTraitSimGraphics) + { + // This is ok without calling gameModel->view->PauseRendererThread() because + // the renderer thread gets paused anyway if there are handlers + // installed for eventTraitSimGraphics and *SimDraw events. + return ren; + } + return g; + } std::vector customElements; // must come after luaState diff --git a/src/lua/PlainCommandInterface.cpp b/src/lua/PlainCommandInterface.cpp index e8ac71cd3..707af8765 100644 --- a/src/lua/PlainCommandInterface.cpp +++ b/src/lua/PlainCommandInterface.cpp @@ -23,6 +23,11 @@ bool CommandInterface::HandleEvent(const GameControllerEvent &event) return true; } +bool CommandInterface::HaveSimGraphicsEventHandlers() +{ + return false; +} + int CommandInterface::Command(String command) { return PlainCommand(command); diff --git a/src/simulation/SaveRenderer.cpp b/src/simulation/SaveRenderer.cpp index fad7d3379..444dd4b11 100644 --- a/src/simulation/SaveRenderer.cpp +++ b/src/simulation/SaveRenderer.cpp @@ -13,32 +13,24 @@ SaveRenderer::SaveRenderer() sim = std::make_unique(); ren = std::make_unique(); ren->sim = sim.get(); - ren->decorations_enable = true; - ren->blackDecorations = true; } SaveRenderer::~SaveRenderer() = default; -std::unique_ptr SaveRenderer::Render(const GameSave *save, bool decorations, bool fire, Renderer *renderModeSource) +std::unique_ptr SaveRenderer::Render(const GameSave *save, bool decorations, bool fire, RendererSettings rendererSettings) { // this function usually runs on a thread different from where element info in SimulationData may be written, so we acquire a read-only lock on it auto &sd = SimulationData::CRef(); std::shared_lock lk(sd.elementGraphicsMx); std::lock_guard gx(renderMutex); - ren->ResetModes(); - if (renderModeSource) - { - ren->SetRenderMode(renderModeSource->GetRenderMode()); - ren->SetDisplayMode(renderModeSource->GetDisplayMode()); - ren->SetColorMode(renderModeSource->GetColorMode()); - } + rendererSettings.decorations_enable = true; + rendererSettings.blackDecorations = !decorations; + ren->ApplySettings(rendererSettings); sim->clear_sim(); sim->Load(save, true, { 0, 0 }); - ren->decorations_enable = true; - ren->blackDecorations = !decorations; ren->ClearAccumulation(); ren->clearScreen(); diff --git a/src/simulation/SaveRenderer.h b/src/simulation/SaveRenderer.h index a5fffca98..9e058f168 100644 --- a/src/simulation/SaveRenderer.h +++ b/src/simulation/SaveRenderer.h @@ -4,6 +4,7 @@ #include #include #include "common/ExplicitSingleton.h" +#include "graphics/RendererSettings.h" #include "common/String.h" class GameSave; @@ -20,5 +21,5 @@ class SaveRenderer: public ExplicitSingleton public: SaveRenderer(); ~SaveRenderer(); - std::unique_ptr Render(const GameSave *save, bool decorations = true, bool fire = true, Renderer *renderModeSource = nullptr); + std::unique_ptr Render(const GameSave *save, bool decorations, bool fire, RendererSettings rendererSettings); };