1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-15 11:03:00 +01:00
Files
OpenRCT2/src/openrct2-ui/drawing/engines/HardwareDisplayDrawingEngine.cpp
Michał Janiszewski 0e9982d6ca Drop "Pause game when Steam overlay is open" feature
As shown in https://github.com/OpenRCT2/OpenRCT2/pull/24618, the steam
overlay feature doesn't work well and can cause jarring user experience
in some scenarios. It is only supported in the software renderer and
with code moving steadily to OpenGL, the time has come to remove a
broken feature.
2025-06-16 21:57:56 +02:00

381 lines
13 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2025 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "DrawingEngineFactory.hpp"
#include <SDL.h>
#include <cmath>
#include <memory>
#include <openrct2/Diagnostic.h>
#include <openrct2/Game.h>
#include <openrct2/config/Config.h>
#include <openrct2/core/Guard.hpp>
#include <openrct2/drawing/IDrawingEngine.h>
#include <openrct2/drawing/LightFX.h>
#include <openrct2/drawing/X8DrawingEngine.h>
#include <openrct2/paint/Paint.h>
#include <openrct2/ui/UiContext.h>
#include <vector>
using namespace OpenRCT2;
using namespace OpenRCT2::Drawing;
using namespace OpenRCT2::Ui;
class HardwareDisplayDrawingEngine final : public X8DrawingEngine
{
private:
constexpr static uint32_t kDirtyVisualTime = 40;
constexpr static uint32_t kDirtyRegionAlpha = 100;
IUiContext& _uiContext;
SDL_Window* _window = nullptr;
SDL_Renderer* _sdlRenderer = nullptr;
SDL_Texture* _screenTexture = nullptr;
SDL_Texture* _scaledScreenTexture = nullptr;
SDL_PixelFormat* _screenTextureFormat = nullptr;
uint32_t _paletteHWMapped[256] = { 0 };
uint32_t _lightPaletteHWMapped[256] = { 0 };
bool _useVsync = true;
std::vector<uint32_t> _dirtyVisualsTime;
bool smoothNN = false;
public:
explicit HardwareDisplayDrawingEngine(IUiContext& uiContext)
: X8DrawingEngine(uiContext)
, _uiContext(uiContext)
{
_window = static_cast<SDL_Window*>(_uiContext.GetWindow());
}
~HardwareDisplayDrawingEngine() override
{
if (_screenTexture != nullptr)
{
SDL_DestroyTexture(_screenTexture);
}
if (_scaledScreenTexture != nullptr)
{
SDL_DestroyTexture(_scaledScreenTexture);
}
SDL_FreeFormat(_screenTextureFormat);
SDL_DestroyRenderer(_sdlRenderer);
}
void Initialise() override
{
_sdlRenderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED | (_useVsync ? SDL_RENDERER_PRESENTVSYNC : 0));
}
void SetVSync(bool vsync) override
{
if (_useVsync != vsync)
{
_useVsync = vsync;
#if SDL_VERSION_ATLEAST(2, 0, 18)
SDL_RenderSetVSync(_sdlRenderer, vsync ? 1 : 0);
#else
SDL_DestroyRenderer(_sdlRenderer);
_screenTexture = nullptr;
_scaledScreenTexture = nullptr;
Initialise();
Resize(_uiContext->GetWidth(), _uiContext->GetHeight());
#endif
}
}
void Resize(uint32_t width, uint32_t height) override
{
if (width == 0 || height == 0)
{
return;
}
if (_screenTexture != nullptr)
{
SDL_DestroyTexture(_screenTexture);
}
SDL_FreeFormat(_screenTextureFormat);
SDL_RendererInfo rendererInfo = {};
int32_t result = SDL_GetRendererInfo(_sdlRenderer, &rendererInfo);
if (result < 0)
{
LOG_WARNING("HWDisplayDrawingEngine::Resize error: %s", SDL_GetError());
return;
}
uint32_t pixelFormat = SDL_PIXELFORMAT_UNKNOWN;
for (uint32_t i = 0; i < rendererInfo.num_texture_formats; i++)
{
uint32_t format = rendererInfo.texture_formats[i];
if (!SDL_ISPIXELFORMAT_FOURCC(format) && !SDL_ISPIXELFORMAT_INDEXED(format)
&& (pixelFormat == SDL_PIXELFORMAT_UNKNOWN || SDL_BYTESPERPIXEL(format) < SDL_BYTESPERPIXEL(pixelFormat)))
{
pixelFormat = format;
}
}
ScaleQuality scaleQuality = GetContext()->GetUiContext().GetScaleQuality();
if (scaleQuality == ScaleQuality::SmoothNearestNeighbour)
{
scaleQuality = ScaleQuality::Linear;
smoothNN = true;
}
else
{
smoothNN = false;
}
if (smoothNN)
{
if (_scaledScreenTexture != nullptr)
{
SDL_DestroyTexture(_scaledScreenTexture);
}
char scaleQualityBuffer[4];
snprintf(scaleQualityBuffer, sizeof(scaleQualityBuffer), "%d", static_cast<int32_t>(scaleQuality));
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
_screenTexture = SDL_CreateTexture(_sdlRenderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, width, height);
Guard::Assert(
_screenTexture != nullptr, "Failed to create unscaled screen texture (%ux%u, pixelFormat = %u): %s", width,
height, pixelFormat, SDL_GetError());
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, scaleQualityBuffer);
uint32_t scale = std::ceil(Config::Get().general.WindowScale);
_scaledScreenTexture = SDL_CreateTexture(
_sdlRenderer, pixelFormat, SDL_TEXTUREACCESS_TARGET, width * scale, height * scale);
Guard::Assert(
_scaledScreenTexture != nullptr,
"Failed to create scaled screen texture (%ux%u, scale = %u, pixelFormat = %u): %s", width, height, scale,
pixelFormat, SDL_GetError());
}
else
{
_screenTexture = SDL_CreateTexture(_sdlRenderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, width, height);
Guard::Assert(
_screenTexture != nullptr, "Failed to create screen texture (%ux%u, pixelFormat = %u): %s", width, height,
pixelFormat, SDL_GetError());
}
uint32_t format;
SDL_QueryTexture(_screenTexture, &format, nullptr, nullptr, nullptr);
_screenTextureFormat = SDL_AllocFormat(format);
X8DrawingEngine::Resize(width, height);
}
void SetPalette(const GamePalette& palette) override
{
if (_screenTextureFormat != nullptr)
{
for (int32_t i = 0; i < 256; i++)
{
_paletteHWMapped[i] = SDL_MapRGB(_screenTextureFormat, palette[i].Red, palette[i].Green, palette[i].Blue);
}
if (Config::Get().general.EnableLightFx)
{
auto& lightPalette = LightFx::GetPalette();
for (int32_t i = 0; i < 256; i++)
{
const auto& src = lightPalette[i];
_lightPaletteHWMapped[i] = SDL_MapRGBA(_screenTextureFormat, src.Red, src.Green, src.Blue, src.Alpha);
}
}
}
}
void BeginDraw() override
{
X8DrawingEngine::BeginDraw();
}
void EndDraw() override
{
X8DrawingEngine::EndDraw();
Display();
}
protected:
void OnDrawDirtyBlock(int32_t left, int32_t top, int32_t right, int32_t bottom) override
{
if (gShowDirtyVisuals)
{
const auto columns = ((right - left) + (_invalidationGrid.getBlockWidth() - 1)) / _invalidationGrid.getBlockWidth();
const auto rows = ((bottom - top) + (_invalidationGrid.getBlockHeight() - 1)) / _invalidationGrid.getBlockHeight();
const auto firstRow = top / _invalidationGrid.getBlockHeight();
const auto firstColumn = left / _invalidationGrid.getBlockWidth();
for (uint32_t y = 0; y < rows; y++)
{
for (uint32_t x = 0; x < columns; x++)
{
SetDirtyVisualTime(firstColumn + x, firstRow + y, gCurrentRealTimeTicks + kDirtyVisualTime);
}
}
}
}
private:
void Display()
{
if (Config::Get().general.EnableLightFx)
{
void* pixels;
int32_t pitch;
if (SDL_LockTexture(_screenTexture, nullptr, &pixels, &pitch) == 0)
{
LightFx::RenderToTexture(pixels, pitch, _bits, _width, _height, _paletteHWMapped, _lightPaletteHWMapped);
SDL_UnlockTexture(_screenTexture);
}
}
else
{
CopyBitsToTexture(
_screenTexture, _bits, static_cast<int32_t>(_width), static_cast<int32_t>(_height), _paletteHWMapped);
}
if (smoothNN)
{
SDL_SetRenderTarget(_sdlRenderer, _scaledScreenTexture);
SDL_RenderCopy(_sdlRenderer, _screenTexture, nullptr, nullptr);
SDL_SetRenderTarget(_sdlRenderer, nullptr);
SDL_RenderCopy(_sdlRenderer, _scaledScreenTexture, nullptr, nullptr);
}
else
{
SDL_RenderCopy(_sdlRenderer, _screenTexture, nullptr, nullptr);
}
if (gShowDirtyVisuals)
{
RenderDirtyVisuals();
}
SDL_RenderPresent(_sdlRenderer);
}
void CopyBitsToTexture(SDL_Texture* texture, uint8_t* src, int32_t width, int32_t height, const uint32_t* palette)
{
void* pixels;
int32_t pitch;
if (SDL_LockTexture(texture, nullptr, &pixels, &pitch) == 0)
{
int32_t padding = pitch - (width * 4);
if (pitch == width * 4)
{
uint32_t* dst = static_cast<uint32_t*>(pixels);
for (int32_t i = width * height; i > 0; i--)
{
*dst++ = palette[*src++];
}
}
else
{
if (pitch == (width * 2) + padding)
{
uint16_t* dst = static_cast<uint16_t*>(pixels);
for (int32_t y = height; y > 0; y--)
{
for (int32_t x = width; x > 0; x--)
{
const uint8_t lower = *reinterpret_cast<const uint8_t*>(&palette[*src++]);
const uint8_t upper = *reinterpret_cast<const uint8_t*>(&palette[*src++]);
*dst++ = (lower << 8) | upper;
}
dst = reinterpret_cast<uint16_t*>(reinterpret_cast<uint8_t*>(dst) + padding);
}
}
else if (pitch == width + padding)
{
uint8_t* dst = static_cast<uint8_t*>(pixels);
for (int32_t y = height; y > 0; y--)
{
for (int32_t x = width; x > 0; x--)
{
*dst++ = *reinterpret_cast<const uint8_t*>(&palette[*src++]);
}
dst += padding;
}
}
}
SDL_UnlockTexture(texture);
}
}
uint32_t GetDirtyVisualTime(uint32_t x, uint32_t y)
{
uint32_t result = 0;
uint32_t i = y * _invalidationGrid.getColumnCount() + x;
if (_dirtyVisualsTime.size() > i)
{
result = _dirtyVisualsTime[i];
}
return result;
}
void SetDirtyVisualTime(uint32_t x, uint32_t y, uint32_t value)
{
const auto rows = _invalidationGrid.getRowCount();
const auto columns = _invalidationGrid.getColumnCount();
_dirtyVisualsTime.resize(rows * columns);
uint32_t i = y * _invalidationGrid.getColumnCount() + x;
if (_dirtyVisualsTime.size() > i)
{
_dirtyVisualsTime[i] = value;
}
}
void RenderDirtyVisuals()
{
int windowX, windowY, renderX, renderY;
SDL_GetWindowSize(_window, &windowX, &windowY);
SDL_GetRendererOutputSize(_sdlRenderer, &renderX, &renderY);
float scaleX = Config::Get().general.WindowScale * renderX / static_cast<float>(windowX);
float scaleY = Config::Get().general.WindowScale * renderY / static_cast<float>(windowY);
SDL_SetRenderDrawBlendMode(_sdlRenderer, SDL_BLENDMODE_BLEND);
for (uint32_t y = 0; y < _invalidationGrid.getRowCount(); y++)
{
for (uint32_t x = 0; x < _invalidationGrid.getColumnCount(); x++)
{
const auto timeEnd = GetDirtyVisualTime(x, y);
const auto timeLeft = gCurrentRealTimeTicks < timeEnd ? timeEnd - gCurrentRealTimeTicks : 0;
if (timeLeft > 0)
{
uint8_t alpha = timeLeft * kDirtyRegionAlpha / kDirtyVisualTime;
SDL_Rect ddRect;
ddRect.x = static_cast<int32_t>(x * _invalidationGrid.getBlockWidth() * scaleX);
ddRect.y = static_cast<int32_t>(y * _invalidationGrid.getBlockHeight() * scaleY);
ddRect.w = static_cast<int32_t>(_invalidationGrid.getBlockWidth() * scaleX);
ddRect.h = static_cast<int32_t>(_invalidationGrid.getBlockHeight() * scaleY);
SDL_SetRenderDrawColor(_sdlRenderer, 255, 255, 255, alpha);
SDL_RenderFillRect(_sdlRenderer, &ddRect);
}
}
}
}
};
std::unique_ptr<IDrawingEngine> OpenRCT2::Ui::CreateHardwareDisplayDrawingEngine(IUiContext& uiContext)
{
return std::make_unique<HardwareDisplayDrawingEngine>(uiContext);
}