mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-15 11:03:00 +01:00
Introduce InvalidationGrid
This commit is contained in:
74
src/openrct2/drawing/InvalidationGrid.cpp
Normal file
74
src/openrct2/drawing/InvalidationGrid.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "InvalidationGrid.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace OpenRCT2::Drawing
|
||||
{
|
||||
uint32_t InvalidationGrid::getRowCount() const noexcept
|
||||
{
|
||||
return _rowCount;
|
||||
}
|
||||
|
||||
uint32_t InvalidationGrid::getColumnCount() const noexcept
|
||||
{
|
||||
return _columnCount;
|
||||
}
|
||||
|
||||
uint32_t InvalidationGrid::getBlockWidth() const noexcept
|
||||
{
|
||||
return _blockWidth;
|
||||
}
|
||||
|
||||
uint32_t InvalidationGrid::getBlockHeight() const noexcept
|
||||
{
|
||||
return _blockHeight;
|
||||
}
|
||||
|
||||
void InvalidationGrid::reset(int32_t width, int32_t height, uint32_t blockWidth, uint32_t blockHeight) noexcept
|
||||
{
|
||||
_blockWidth = blockWidth;
|
||||
_blockHeight = blockHeight;
|
||||
_columnCount = (width / blockWidth) + 1;
|
||||
_rowCount = (height / blockHeight) + 1;
|
||||
_screenWidth = width;
|
||||
_screenHeight = height;
|
||||
}
|
||||
|
||||
void InvalidationGrid::invalidate(int32_t left, int32_t top, int32_t right, int32_t bottom) noexcept
|
||||
{
|
||||
left = std::max(left, 0);
|
||||
top = std::max(top, 0);
|
||||
right = std::min(right, static_cast<int32_t>(_screenWidth));
|
||||
bottom = std::min(bottom, static_cast<int32_t>(_screenHeight));
|
||||
|
||||
if (left >= right)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (top >= bottom)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
left /= _blockWidth;
|
||||
right /= _blockWidth;
|
||||
|
||||
top /= _blockHeight;
|
||||
bottom /= _blockHeight;
|
||||
|
||||
// TODO: Remove this once _blocks is no longer interop wrapper.
|
||||
auto& blocks = _blocks;
|
||||
|
||||
const auto columnSize = right - left + 1;
|
||||
|
||||
for (int16_t y = top; y <= bottom; y++)
|
||||
{
|
||||
const auto yOffset = y * _columnCount;
|
||||
|
||||
// Mark row by column size as invalidated.
|
||||
std::memset(blocks + yOffset + left, 0xFF, columnSize);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OpenRCT2::Drawing
|
||||
85
src/openrct2/drawing/InvalidationGrid.h
Normal file
85
src/openrct2/drawing/InvalidationGrid.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace OpenRCT2::Drawing
|
||||
{
|
||||
class InvalidationGrid
|
||||
{
|
||||
uint16_t _blockWidth{};
|
||||
uint16_t _blockHeight{};
|
||||
uint32_t _columnCount{};
|
||||
uint32_t _rowCount{};
|
||||
|
||||
uint8_t _blocks[7500]{};
|
||||
uint32_t _screenWidth{};
|
||||
uint32_t _screenHeight{};
|
||||
|
||||
public:
|
||||
uint32_t getRowCount() const noexcept;
|
||||
|
||||
uint32_t getColumnCount() const noexcept;
|
||||
|
||||
uint32_t getBlockWidth() const noexcept;
|
||||
|
||||
uint32_t getBlockHeight() const noexcept;
|
||||
|
||||
void reset(int32_t width, int32_t height, uint32_t blockWidth, uint32_t blockHeight) noexcept;
|
||||
|
||||
void invalidate(int32_t left, int32_t top, int32_t right, int32_t bottom) noexcept;
|
||||
|
||||
template<typename F>
|
||||
void traverseDirtyCells(F&& func)
|
||||
{
|
||||
const auto columnCount = _columnCount;
|
||||
const auto rowCount = _rowCount;
|
||||
const auto blockWidth = _blockWidth;
|
||||
const auto blockHeight = _blockHeight;
|
||||
auto& blocks = _blocks;
|
||||
|
||||
for (uint32_t column = 0; column < columnCount; column++)
|
||||
{
|
||||
for (uint32_t row = 0; row < rowCount; row++)
|
||||
{
|
||||
const auto rowStartOffset = row * columnCount;
|
||||
if (blocks[rowStartOffset + column] != 0)
|
||||
{
|
||||
uint32_t rowEndOffset = rowStartOffset;
|
||||
uint32_t numRowsDirty = 0;
|
||||
|
||||
// Count amount of dirty rows at current column.
|
||||
while (true)
|
||||
{
|
||||
if (row + numRowsDirty + 1 >= rowCount || blocks[rowEndOffset + column + columnCount] == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
numRowsDirty++;
|
||||
rowEndOffset += columnCount;
|
||||
}
|
||||
|
||||
// Clear rows at the current column.
|
||||
for (auto rowOffset = rowStartOffset; rowOffset <= rowEndOffset; rowOffset += columnCount)
|
||||
{
|
||||
blocks[rowOffset + column] = 0;
|
||||
}
|
||||
|
||||
// Convert to pixel coordinates.
|
||||
const auto left = column * blockWidth;
|
||||
const auto top = row * blockHeight;
|
||||
const auto right = (column + 1) * blockWidth;
|
||||
const auto bottom = (row + numRowsDirty + 1) * blockHeight;
|
||||
|
||||
if (left < _screenWidth && top < _screenHeight)
|
||||
{
|
||||
func(left, top, std::min(right, _screenWidth), std::min(bottom, _screenHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace OpenRCT2::Drawing
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Drawing.h"
|
||||
#include "IDrawingContext.h"
|
||||
#include "IDrawingEngine.h"
|
||||
#include "InvalidationGrid.h"
|
||||
#include "LightFX.h"
|
||||
#include "Weather.h"
|
||||
|
||||
@@ -153,34 +154,7 @@ void X8DrawingEngine::SetVSync([[maybe_unused]] bool vsync)
|
||||
|
||||
void X8DrawingEngine::Invalidate(int32_t left, int32_t top, int32_t right, int32_t bottom)
|
||||
{
|
||||
left = std::max(left, 0);
|
||||
top = std::max(top, 0);
|
||||
right = std::min(right, static_cast<int32_t>(_width));
|
||||
bottom = std::min(bottom, static_cast<int32_t>(_height));
|
||||
|
||||
if (left >= right)
|
||||
return;
|
||||
if (top >= bottom)
|
||||
return;
|
||||
|
||||
right--;
|
||||
bottom--;
|
||||
|
||||
left >>= _dirtyGrid.BlockShiftX;
|
||||
right >>= _dirtyGrid.BlockShiftX;
|
||||
top >>= _dirtyGrid.BlockShiftY;
|
||||
bottom >>= _dirtyGrid.BlockShiftY;
|
||||
|
||||
uint32_t dirtyBlockColumns = _dirtyGrid.BlockColumns;
|
||||
uint8_t* screenDirtyBlocks = _dirtyGrid.Blocks;
|
||||
for (int16_t y = top; y <= bottom; y++)
|
||||
{
|
||||
uint32_t yOffset = y * dirtyBlockColumns;
|
||||
for (int16_t x = left; x <= right; x++)
|
||||
{
|
||||
screenDirtyBlocks[yOffset + x] = 0xFF;
|
||||
}
|
||||
}
|
||||
_invalidationGrid.invalidate(left, top, right, bottom);
|
||||
}
|
||||
|
||||
void X8DrawingEngine::BeginDraw()
|
||||
@@ -352,103 +326,24 @@ void X8DrawingEngine::OnDrawDirtyBlock(
|
||||
|
||||
void X8DrawingEngine::ConfigureDirtyGrid()
|
||||
{
|
||||
_dirtyGrid.BlockShiftX = 7;
|
||||
_dirtyGrid.BlockShiftY = 5; // Keep column at 32 (1 << 5)
|
||||
_dirtyGrid.BlockWidth = 1 << _dirtyGrid.BlockShiftX;
|
||||
_dirtyGrid.BlockHeight = 1 << _dirtyGrid.BlockShiftY;
|
||||
_dirtyGrid.BlockColumns = (_width >> _dirtyGrid.BlockShiftX) + 1;
|
||||
_dirtyGrid.BlockRows = (_height >> _dirtyGrid.BlockShiftY) + 1;
|
||||
const auto blockWidth = 1u << 7;
|
||||
const auto blockHeight = 1u << 5;
|
||||
|
||||
delete[] _dirtyGrid.Blocks;
|
||||
_dirtyGrid.Blocks = new uint8_t[_dirtyGrid.BlockColumns * _dirtyGrid.BlockRows];
|
||||
_invalidationGrid.reset(_width, _height, blockWidth, blockHeight);
|
||||
}
|
||||
|
||||
void X8DrawingEngine::DrawAllDirtyBlocks()
|
||||
{
|
||||
// TODO: For optimal performance it is currently limited to a single column.
|
||||
// The optimal approach would be to extract all dirty regions as rectangles not including
|
||||
// parts that are not marked dirty and have the grid more fine grained.
|
||||
// A situation like following:
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7 8 9
|
||||
// 1 - - - - - - - - -
|
||||
// 2 - x x x x - - - -
|
||||
// 3 - x x - - - - - -
|
||||
// 4 - - - - - - - - -
|
||||
// 5 - - - - - - - - -
|
||||
// 6 - - - - - - - - -
|
||||
// 7 - - - - - - - - -
|
||||
// 8 - - - - - - - - -
|
||||
// 9 - - - - - - - - -
|
||||
//
|
||||
// Would currently redraw {2,2} to {3,5} where {3,4} and {3,5} are not dirty. Choosing to do this
|
||||
// per column eliminates this issue but limits it to rendering just a single column at a time.
|
||||
|
||||
for (uint32_t x = 0; x < _dirtyGrid.BlockColumns; x++)
|
||||
{
|
||||
for (uint32_t y = 0; y < _dirtyGrid.BlockRows; y++)
|
||||
{
|
||||
uint32_t yOffset = y * _dirtyGrid.BlockColumns;
|
||||
if (_dirtyGrid.Blocks[yOffset + x] == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// See comment above as to why this is 1.
|
||||
const uint32_t columns = 1;
|
||||
|
||||
// Check rows
|
||||
auto rows = GetNumDirtyRows(x, y, columns);
|
||||
DrawDirtyBlocks(x, y, columns, rows);
|
||||
}
|
||||
}
|
||||
_invalidationGrid.traverseDirtyCells(
|
||||
[this](int32_t left, int32_t top, int32_t right, int32_t bottom) {
|
||||
DrawDirtyBlocks(left, top, right, bottom);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t X8DrawingEngine::GetNumDirtyRows(const uint32_t x, const uint32_t y, const uint32_t columns)
|
||||
void X8DrawingEngine::DrawDirtyBlocks(int32_t left, int32_t top, int32_t right, int32_t bottom)
|
||||
{
|
||||
uint32_t yy = y;
|
||||
|
||||
for (yy = y; yy < _dirtyGrid.BlockRows; yy++)
|
||||
{
|
||||
uint32_t yyOffset = yy * _dirtyGrid.BlockColumns;
|
||||
for (uint32_t xx = x; xx < x + columns; xx++)
|
||||
{
|
||||
if (_dirtyGrid.Blocks[yyOffset + xx] == 0)
|
||||
{
|
||||
return yy - y;
|
||||
}
|
||||
}
|
||||
}
|
||||
return yy - y;
|
||||
}
|
||||
|
||||
void X8DrawingEngine::DrawDirtyBlocks(uint32_t x, uint32_t y, uint32_t columns, uint32_t rows)
|
||||
{
|
||||
uint32_t dirtyBlockColumns = _dirtyGrid.BlockColumns;
|
||||
uint8_t* screenDirtyBlocks = _dirtyGrid.Blocks;
|
||||
|
||||
// Unset dirty blocks
|
||||
for (uint32_t top = y; top < y + rows; top++)
|
||||
{
|
||||
uint32_t topOffset = top * dirtyBlockColumns;
|
||||
for (uint32_t left = x; left < x + columns; left++)
|
||||
{
|
||||
screenDirtyBlocks[topOffset + left] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine region in pixels
|
||||
uint32_t left = std::max<uint32_t>(0, x * _dirtyGrid.BlockWidth);
|
||||
uint32_t top = std::max<uint32_t>(0, y * _dirtyGrid.BlockHeight);
|
||||
uint32_t right = std::min(_width, left + (columns * _dirtyGrid.BlockWidth));
|
||||
uint32_t bottom = std::min(_height, top + (rows * _dirtyGrid.BlockHeight));
|
||||
if (right <= left || bottom <= top)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw region
|
||||
OnDrawDirtyBlock(x, y, columns, rows);
|
||||
//OnDrawDirtyBlock(x, y, columns, rows);
|
||||
WindowDrawAll(_bitsDPI, left, top, right, bottom);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "IDrawingContext.h"
|
||||
#include "IDrawingEngine.h"
|
||||
#include "InvalidationGrid.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -81,6 +82,7 @@ namespace OpenRCT2
|
||||
|
||||
X8WeatherDrawer _weatherDrawer;
|
||||
X8DrawingContext* _drawingContext;
|
||||
InvalidationGrid _invalidationGrid;
|
||||
|
||||
public:
|
||||
explicit X8DrawingEngine(const std::shared_ptr<Ui::IUiContext>& uiContext);
|
||||
@@ -120,8 +122,7 @@ namespace OpenRCT2
|
||||
private:
|
||||
void ConfigureDirtyGrid();
|
||||
void DrawAllDirtyBlocks();
|
||||
uint32_t GetNumDirtyRows(const uint32_t x, const uint32_t y, const uint32_t columns);
|
||||
void DrawDirtyBlocks(uint32_t x, uint32_t y, uint32_t columns, uint32_t rows);
|
||||
void DrawDirtyBlocks(int32_t left, int32_t top, int32_t right, int32_t bottom);
|
||||
};
|
||||
#ifdef __WARN_SUGGEST_FINAL_TYPES__
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
@@ -249,6 +249,7 @@
|
||||
<ClInclude Include="drawing\Image.h" />
|
||||
<ClInclude Include="drawing\ImageId.hpp" />
|
||||
<ClInclude Include="drawing\ImageImporter.h" />
|
||||
<ClInclude Include="drawing\InvalidationGrid.h" />
|
||||
<ClInclude Include="drawing\LightFX.h" />
|
||||
<ClInclude Include="drawing\NewDrawing.h" />
|
||||
<ClInclude Include="drawing\ScrollingText.h" />
|
||||
@@ -805,6 +806,7 @@
|
||||
<ClCompile Include="drawing\Font.cpp" />
|
||||
<ClCompile Include="drawing\Image.cpp" />
|
||||
<ClCompile Include="drawing\ImageImporter.cpp" />
|
||||
<ClCompile Include="drawing\InvalidationGrid.cpp" />
|
||||
<ClCompile Include="drawing\LightFX.cpp" />
|
||||
<ClCompile Include="drawing\Line.cpp" />
|
||||
<ClCompile Include="drawing\NewDrawing.cpp" />
|
||||
|
||||
Reference in New Issue
Block a user