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.
This commit is contained in:
Tamás Bálint Misius
2024-08-20 22:13:32 +02:00
parent 77edec62d1
commit 820f44a716
26 changed files with 526 additions and 378 deletions

View File

@@ -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)

View File

@@ -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);

View File

@@ -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 <cstdint>
#include <optional>
#include <memory>
@@ -13,13 +12,13 @@
#include <vector>
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<Renderer>
class Renderer : private RendererSettings, public RasterDrawMethods<Renderer>
{
RendererFrame video;
std::array<pixel, WINDOW.X * RES.Y> persistentVideo;
@@ -41,39 +40,24 @@ class Renderer: public RasterDrawMethods<Renderer>
friend struct RasterDrawMethods<Renderer>;
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<RenderPreset> 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> findingElement;
int foundElements;
//Mouse position for debug information
ui::Point mousePos;
//Renderers
void RenderSimulation();
void DrawBlob(Vec2<int> pos, RGB<uint8_t> 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<VideoBuffer> WallIcon(int wallID, Vec2<int> size);
Renderer();
@@ -140,7 +104,4 @@ public:
RENDERER_TABLE(firwTable)
#undef RENDERER_TABLE
static void PopulateTables();
private:
int gridSize;
};

View File

@@ -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<RendererSettings &>(*this) = newSettings;
}
template struct RasterDrawMethods<Renderer>;

View File

@@ -0,0 +1,23 @@
#pragma once
#include "gui/interface/Point.h"
#include "simulation/ElementGraphics.h"
#include "FindingElement.h"
#include <cstdint>
#include <optional>
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> 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;
};

View File

@@ -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();
}

View File

@@ -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<GameController>
{
CommandInterfacePtr commandInterface;
@@ -204,4 +205,5 @@ public:
void BeforeSimDraw();
void AfterSimDraw();
bool HaveSimGraphicsEventHandlers();
};

View File

@@ -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)

View File

@@ -2,6 +2,7 @@
#include "gui/interface/Colour.h"
#include "client/User.h"
#include "gui/interface/Point.h"
#include "graphics/RendererSettings.h"
#include <vector>
#include <deque>
#include <memory>
@@ -66,6 +67,7 @@ private:
Simulation * sim;
Renderer * ren;
RendererSettings rendererSettings;
std::vector<Menu*> menuList;
std::vector<QuickOption*> 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);

View File

@@ -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 = &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.X<selectPoint1.X)?selectPoint2.X:selectPoint1.X;
int y1 = (selectPoint2.Y<selectPoint1.Y)?selectPoint2.Y:selectPoint1.Y;
if(x2>XRES-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<char> 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<std::pair<String, int> >::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.X<selectPoint1.X)?selectPoint2.X:selectPoint1.X;
int y1 = (selectPoint2.Y<selectPoint1.Y)?selectPoint2.Y:selectPoint1.Y;
if(x2>XRES-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<char> 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<std::pair<String, int> >::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<RenderableSimulation>();
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;
});
}

View File

@@ -9,6 +9,9 @@
#include <memory>
#include <vector>
#include <optional>
#include <thread>
#include <mutex>
#include <condition_variable>
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<int> PlaceSavePos() const;
std::optional<FindingElement> 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<RenderableSimulation> 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);
};

View File

@@ -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;

View File

@@ -5,7 +5,7 @@
#include "Controller.h"
RenderController::RenderController(Simulation *sim, Renderer * ren, std::function<void ()> onDone_):
RenderController::RenderController(Simulation *sim, Renderer * ren, RendererSettings *rendererSettings, std::function<void ()> 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_;
}

View File

@@ -5,6 +5,7 @@
class RenderView;
class RenderModel;
class Renderer;
struct RendererSettings;
class Simulation;
class RenderController
{
@@ -13,7 +14,7 @@ class RenderController
std::function<void ()> onDone;
public:
bool HasExited;
RenderController(Simulation *sim, Renderer * ren, std::function<void ()> onDone = nullptr);
RenderController(Simulation *sim, Renderer * ren, RendererSettings *rendererSettings, std::function<void ()> onDone = nullptr);
void Exit();
RenderView * GetView() { return renderView; }
virtual ~RenderController();

View File

@@ -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;

View File

@@ -4,11 +4,13 @@
class RenderView;
class Renderer;
struct RendererSettings;
class Simulation;
class RenderModel
{
std::vector<RenderView*> 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();

View File

@@ -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);

View File

@@ -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<ModeCheckbox *> renderModes;
std::vector<ModeCheckbox *> displayModes;

View File

@@ -34,6 +34,7 @@ public:
void Init();
bool HandleEvent(const GameControllerEvent &event);
bool HaveSimGraphicsEventHandlers();
int Command(String command);
String FormatCommand(String command);

View File

@@ -11,16 +11,6 @@ static int32_t int32Truncate(double n)
return int32_t(n);
}
static std::variant<Graphics *, Renderer *> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(r, g, b, a));
}, currentGraphics());
}, GetLSI()->GetGraphics());
return 0;
}

View File

@@ -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;
}

View File

@@ -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<GameControllerEvent>)
{
@@ -417,6 +417,42 @@ bool CommandInterface::HandleEvent(const GameControllerEvent &event)
return cont;
}
template<size_t Index>
std::enable_if_t<Index != std::variant_size_v<GameControllerEvent>, bool> HaveSimGraphicsEventHandlersHelper(lua_State *L, std::vector<LuaSmartRef> &gameControllerEventHandlers)
{
if (std::variant_alternative_t<Index, GameControllerEvent>::traits & eventTraitSimGraphics)
{
gameControllerEventHandlers[Index].Push(L);
auto have = lua_objlen(L, -1) > 0;
lua_pop(L, 1);
if (have)
{
return true;
}
}
return HaveSimGraphicsEventHandlersHelper<Index + 1>(L, gameControllerEventHandlers);
}
template<size_t Index>
std::enable_if_t<Index == std::variant_size_v<GameControllerEvent>, bool> HaveSimGraphicsEventHandlersHelper(lua_State *L, std::vector<LuaSmartRef> &gameControllerEventHandlers)
{
return false;
}
bool CommandInterface::HaveSimGraphicsEventHandlers()
{
auto &sd = SimulationData::CRef();
auto *lsi = static_cast<LuaScriptInterface *>(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<LuaScriptInterface *>(this);
@@ -804,3 +840,4 @@ void CommandInterfaceDeleter::operator ()(CommandInterface *ptr) const
{
delete static_cast<LuaScriptInterface *>(ptr);
}

View File

@@ -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<Graphics *, Renderer *> 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<CustomElement> customElements; // must come after luaState

View File

@@ -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);

View File

@@ -13,32 +13,24 @@ SaveRenderer::SaveRenderer()
sim = std::make_unique<Simulation>();
ren = std::make_unique<Renderer>();
ren->sim = sim.get();
ren->decorations_enable = true;
ren->blackDecorations = true;
}
SaveRenderer::~SaveRenderer() = default;
std::unique_ptr<VideoBuffer> SaveRenderer::Render(const GameSave *save, bool decorations, bool fire, Renderer *renderModeSource)
std::unique_ptr<VideoBuffer> 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<std::mutex> 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();

View File

@@ -4,6 +4,7 @@
#include <utility>
#include <vector>
#include "common/ExplicitSingleton.h"
#include "graphics/RendererSettings.h"
#include "common/String.h"
class GameSave;
@@ -20,5 +21,5 @@ class SaveRenderer: public ExplicitSingleton<SaveRenderer>
public:
SaveRenderer();
~SaveRenderer();
std::unique_ptr<VideoBuffer> Render(const GameSave *save, bool decorations = true, bool fire = true, Renderer *renderModeSource = nullptr);
std::unique_ptr<VideoBuffer> Render(const GameSave *save, bool decorations, bool fire, RendererSettings rendererSettings);
};