mirror of https://github.com/OpenRCT2/OpenRCT2.git
415 lines
14 KiB
C++
415 lines
14 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2024 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/Game.h>
|
|
#include <openrct2/common.h>
|
|
#include <openrct2/config/Config.h>
|
|
#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 DIRTY_VISUAL_TIME = 32;
|
|
|
|
std::shared_ptr<IUiContext> const _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 };
|
|
|
|
// Steam overlay checking
|
|
uint32_t _pixelBeforeOverlay = 0;
|
|
uint32_t _pixelAfterOverlay = 0;
|
|
bool _overlayActive = false;
|
|
bool _pausedBeforeOverlay = false;
|
|
bool _useVsync = true;
|
|
|
|
std::vector<uint32_t> _dirtyVisualsTime;
|
|
|
|
bool smoothNN = false;
|
|
|
|
public:
|
|
explicit HardwareDisplayDrawingEngine(const std::shared_ptr<IUiContext>& uiContext)
|
|
: X8DrawingEngine(uiContext)
|
|
, _uiContext(uiContext)
|
|
{
|
|
_window = static_cast<SDL_Window*>(_uiContext->GetWindow());
|
|
}
|
|
|
|
~HardwareDisplayDrawingEngine() override
|
|
{
|
|
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;
|
|
SDL_DestroyRenderer(_sdlRenderer);
|
|
_screenTexture = nullptr;
|
|
_scaledScreenTexture = nullptr;
|
|
Initialise();
|
|
Resize(_uiContext->GetWidth(), _uiContext->GetHeight());
|
|
}
|
|
}
|
|
|
|
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 screen texture: %s", SDL_GetError());
|
|
|
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, scaleQualityBuffer);
|
|
|
|
uint32_t scale = std::ceil(gConfigGeneral.WindowScale);
|
|
_scaledScreenTexture = SDL_CreateTexture(
|
|
_sdlRenderer, pixelFormat, SDL_TEXTUREACCESS_TARGET, width * scale, height * scale);
|
|
|
|
Guard::Assert(_scaledScreenTexture != nullptr, "Failed to create scaled screen texture: %s", SDL_GetError());
|
|
}
|
|
else
|
|
{
|
|
_screenTexture = SDL_CreateTexture(_sdlRenderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, width, height);
|
|
Guard::Assert(_screenTexture != nullptr, "Failed to create screen texture: %s", SDL_GetError());
|
|
}
|
|
|
|
uint32_t format;
|
|
SDL_QueryTexture(_screenTexture, &format, nullptr, nullptr, nullptr);
|
|
_screenTextureFormat = SDL_AllocFormat(format);
|
|
|
|
ConfigureBits(width, height, width);
|
|
}
|
|
|
|
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 (gConfigGeneral.EnableLightFx)
|
|
{
|
|
auto& lightPalette = LightFXGetPalette();
|
|
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 EndDraw() override
|
|
{
|
|
Display();
|
|
if (gShowDirtyVisuals)
|
|
{
|
|
UpdateDirtyVisuals();
|
|
}
|
|
}
|
|
|
|
protected:
|
|
void OnDrawDirtyBlock(uint32_t left, uint32_t top, uint32_t columns, uint32_t rows) override
|
|
{
|
|
if (gShowDirtyVisuals)
|
|
{
|
|
uint32_t right = left + columns;
|
|
uint32_t bottom = top + rows;
|
|
for (uint32_t x = left; x < right; x++)
|
|
{
|
|
for (uint32_t y = top; y < bottom; y++)
|
|
{
|
|
SetDirtyVisualTime(x, y, DIRTY_VISUAL_TIME);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
void Display()
|
|
{
|
|
if (gConfigGeneral.EnableLightFx)
|
|
{
|
|
void* pixels;
|
|
int32_t pitch;
|
|
if (SDL_LockTexture(_screenTexture, nullptr, &pixels, &pitch) == 0)
|
|
{
|
|
LightFXRenderToTexture(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();
|
|
}
|
|
|
|
bool isSteamOverlayActive = GetContext()->GetUiContext()->IsSteamOverlayActive();
|
|
if (isSteamOverlayActive && gConfigGeneral.SteamOverlayPause)
|
|
{
|
|
OverlayPreRenderCheck();
|
|
}
|
|
|
|
SDL_RenderPresent(_sdlRenderer);
|
|
|
|
if (isSteamOverlayActive && gConfigGeneral.SteamOverlayPause)
|
|
{
|
|
OverlayPostRenderCheck();
|
|
}
|
|
}
|
|
|
|
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 * _dirtyGrid.BlockColumns + x;
|
|
if (_dirtyVisualsTime.size() > i)
|
|
{
|
|
result = _dirtyVisualsTime[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SetDirtyVisualTime(uint32_t x, uint32_t y, uint32_t value)
|
|
{
|
|
uint32_t i = y * _dirtyGrid.BlockColumns + x;
|
|
if (_dirtyVisualsTime.size() > i)
|
|
{
|
|
_dirtyVisualsTime[i] = value;
|
|
}
|
|
}
|
|
|
|
void UpdateDirtyVisuals()
|
|
{
|
|
_dirtyVisualsTime.resize(_dirtyGrid.BlockRows * _dirtyGrid.BlockColumns);
|
|
for (uint32_t y = 0; y < _dirtyGrid.BlockRows; y++)
|
|
{
|
|
for (uint32_t x = 0; x < _dirtyGrid.BlockColumns; x++)
|
|
{
|
|
auto timeLeft = GetDirtyVisualTime(x, y);
|
|
if (timeLeft > 0)
|
|
{
|
|
SetDirtyVisualTime(x, y, timeLeft - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderDirtyVisuals()
|
|
{
|
|
float scaleX = gConfigGeneral.WindowScale;
|
|
float scaleY = gConfigGeneral.WindowScale;
|
|
|
|
SDL_SetRenderDrawBlendMode(_sdlRenderer, SDL_BLENDMODE_BLEND);
|
|
for (uint32_t y = 0; y < _dirtyGrid.BlockRows; y++)
|
|
{
|
|
for (uint32_t x = 0; x < _dirtyGrid.BlockColumns; x++)
|
|
{
|
|
auto timeLeft = GetDirtyVisualTime(x, y);
|
|
if (timeLeft > 0)
|
|
{
|
|
uint8_t alpha = static_cast<uint8_t>(timeLeft * 5 / 2);
|
|
|
|
SDL_Rect ddRect;
|
|
ddRect.x = static_cast<int32_t>(x * _dirtyGrid.BlockWidth * scaleX);
|
|
ddRect.y = static_cast<int32_t>(y * _dirtyGrid.BlockHeight * scaleY);
|
|
ddRect.w = static_cast<int32_t>(_dirtyGrid.BlockWidth * scaleX);
|
|
ddRect.h = static_cast<int32_t>(_dirtyGrid.BlockHeight * scaleY);
|
|
|
|
SDL_SetRenderDrawColor(_sdlRenderer, 255, 255, 255, alpha);
|
|
SDL_RenderFillRect(_sdlRenderer, &ddRect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReadCentrePixel(uint32_t* pixel)
|
|
{
|
|
SDL_Rect centrePixelRegion = { static_cast<int32_t>(_width / 2), static_cast<int32_t>(_height / 2), 1, 1 };
|
|
SDL_RenderReadPixels(_sdlRenderer, ¢rePixelRegion, SDL_PIXELFORMAT_RGBA8888, pixel, sizeof(uint32_t));
|
|
}
|
|
|
|
// Should be called before SDL_RenderPresent to capture frame buffer before Steam overlay is drawn.
|
|
void OverlayPreRenderCheck()
|
|
{
|
|
ReadCentrePixel(&_pixelBeforeOverlay);
|
|
}
|
|
|
|
// Should be called after SDL_RenderPresent, when Steam overlay has had the chance to be drawn.
|
|
void OverlayPostRenderCheck()
|
|
{
|
|
ReadCentrePixel(&_pixelAfterOverlay);
|
|
|
|
// Detect an active Steam overlay by checking if the centre pixel is changed by the grey fade.
|
|
// Will not be triggered by applications rendering to corners, like FRAPS, MSI Afterburner and Friends popups.
|
|
bool newOverlayActive = _pixelBeforeOverlay != _pixelAfterOverlay;
|
|
|
|
// Toggle game pause state consistently with base pause state
|
|
if (!_overlayActive && newOverlayActive)
|
|
{
|
|
_pausedBeforeOverlay = gGamePaused & GAME_PAUSED_NORMAL;
|
|
if (!_pausedBeforeOverlay)
|
|
{
|
|
PauseToggle();
|
|
}
|
|
}
|
|
else if (_overlayActive && !newOverlayActive && !_pausedBeforeOverlay)
|
|
{
|
|
PauseToggle();
|
|
}
|
|
|
|
_overlayActive = newOverlayActive;
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<IDrawingEngine> OpenRCT2::Ui::CreateHardwareDisplayDrawingEngine(const std::shared_ptr<IUiContext>& uiContext)
|
|
{
|
|
return std::make_unique<HardwareDisplayDrawingEngine>(uiContext);
|
|
}
|