diff --git a/src/openrct2/drawing/InvalidationGrid.cpp b/src/openrct2/drawing/InvalidationGrid.cpp new file mode 100644 index 0000000000..01535e8cf9 --- /dev/null +++ b/src/openrct2/drawing/InvalidationGrid.cpp @@ -0,0 +1,74 @@ +#include "InvalidationGrid.h" + +#include +#include + +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(_screenWidth)); + bottom = std::min(bottom, static_cast(_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 diff --git a/src/openrct2/drawing/InvalidationGrid.h b/src/openrct2/drawing/InvalidationGrid.h new file mode 100644 index 0000000000..9423ff97a4 --- /dev/null +++ b/src/openrct2/drawing/InvalidationGrid.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +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 + 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 diff --git a/src/openrct2/drawing/X8DrawingEngine.cpp b/src/openrct2/drawing/X8DrawingEngine.cpp index 82cadbd950..e36e36b3cc 100644 --- a/src/openrct2/drawing/X8DrawingEngine.cpp +++ b/src/openrct2/drawing/X8DrawingEngine.cpp @@ -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(_width)); - bottom = std::min(bottom, static_cast(_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(0, x * _dirtyGrid.BlockWidth); - uint32_t top = std::max(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); } diff --git a/src/openrct2/drawing/X8DrawingEngine.h b/src/openrct2/drawing/X8DrawingEngine.h index d1e3834dab..80ce4d4bf3 100644 --- a/src/openrct2/drawing/X8DrawingEngine.h +++ b/src/openrct2/drawing/X8DrawingEngine.h @@ -11,6 +11,7 @@ #include "IDrawingContext.h" #include "IDrawingEngine.h" +#include "InvalidationGrid.h" #include @@ -81,6 +82,7 @@ namespace OpenRCT2 X8WeatherDrawer _weatherDrawer; X8DrawingContext* _drawingContext; + InvalidationGrid _invalidationGrid; public: explicit X8DrawingEngine(const std::shared_ptr& 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 diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 23119502e5..00ca4e3afd 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -249,6 +249,7 @@ + @@ -805,6 +806,7 @@ +