You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
357 lines
8.2 KiB
357 lines
8.2 KiB
/** |
|
* @file dx.cpp |
|
* |
|
* Implementation of functions setting up the graphics pipeline. |
|
*/ |
|
#include "dx.h" |
|
|
|
#include <SDL.h> |
|
|
|
#include "engine.h" |
|
#include "options.h" |
|
#include "storm/storm.h" |
|
#include "utils/display.h" |
|
#include "utils/log.hpp" |
|
#include "utils/sdl_mutex.h" |
|
#include "utils/sdl_wrap.h" |
|
|
|
#ifdef __3DS__ |
|
#include <3ds.h> |
|
#endif |
|
|
|
namespace devilution { |
|
|
|
int refreshDelay; |
|
SDL_Renderer *renderer; |
|
#ifndef USE_SDL1 |
|
SDLTextureUniquePtr texture; |
|
#endif |
|
|
|
/** Currently active palette */ |
|
SDLPaletteUniquePtr Palette; |
|
unsigned int pal_surface_palette_version = 0; |
|
|
|
/** 24-bit renderer texture surface */ |
|
SDLSurfaceUniquePtr RendererTextureSurface; |
|
|
|
/** 8-bit surface that we render to */ |
|
SDL_Surface *PalSurface; |
|
namespace { |
|
SDLSurfaceUniquePtr PinnedPalSurface; |
|
} // namespace |
|
|
|
/** Whether we render directly to the screen surface, i.e. `PalSurface == GetOutputSurface()` */ |
|
bool RenderDirectlyToOutputSurface; |
|
|
|
namespace { |
|
|
|
int sgdwLockCount; |
|
#ifdef _DEBUG |
|
int locktbl[256]; |
|
#endif |
|
SdlMutex MemCrit; |
|
|
|
bool CanRenderDirectlyToOutputSurface() |
|
{ |
|
#ifdef USE_SDL1 |
|
#ifdef SDL1_FORCE_DIRECT_RENDER |
|
return true; |
|
#else |
|
auto *outputSurface = GetOutputSurface(); |
|
return ((outputSurface->flags & SDL_DOUBLEBUF) == SDL_DOUBLEBUF |
|
&& outputSurface->w == gnScreenWidth && outputSurface->h == gnScreenHeight |
|
&& outputSurface->format->BitsPerPixel == 8); |
|
#endif |
|
#else // !USE_SDL1 |
|
return false; |
|
#endif |
|
} |
|
|
|
void CreateBackBuffer() |
|
{ |
|
if (CanRenderDirectlyToOutputSurface()) { |
|
Log("{}", "Will render directly to the SDL output surface"); |
|
PalSurface = GetOutputSurface(); |
|
RenderDirectlyToOutputSurface = true; |
|
} else { |
|
PinnedPalSurface = SDLWrap::CreateRGBSurfaceWithFormat( |
|
/*flags=*/0, |
|
/*width=*/gnScreenWidth, |
|
/*height=*/gnScreenHeight, |
|
/*depth=*/8, |
|
SDL_PIXELFORMAT_INDEX8); |
|
PalSurface = PinnedPalSurface.get(); |
|
} |
|
|
|
#ifndef USE_SDL1 |
|
// In SDL2, `PalSurface` points to the global `palette`. |
|
if (SDL_SetSurfacePalette(PalSurface, Palette.get()) < 0) |
|
ErrSdl(); |
|
#else |
|
// In SDL1, `PalSurface` owns its palette and we must update it every |
|
// time the global `palette` is changed. No need to do anything here as |
|
// the global `palette` doesn't have any colors set yet. |
|
#endif |
|
|
|
pal_surface_palette_version = 1; |
|
} |
|
|
|
void CreatePrimarySurface() |
|
{ |
|
#ifndef USE_SDL1 |
|
if (renderer != nullptr) { |
|
int width = 0; |
|
int height = 0; |
|
SDL_RenderGetLogicalSize(renderer, &width, &height); |
|
Uint32 format; |
|
if (SDL_QueryTexture(texture.get(), &format, nullptr, nullptr, nullptr) < 0) |
|
ErrSdl(); |
|
RendererTextureSurface = SDLWrap::CreateRGBSurfaceWithFormat(0, width, height, SDL_BITSPERPIXEL(format), format); |
|
} |
|
#endif |
|
if (GetOutputSurface() == nullptr) { |
|
ErrSdl(); |
|
} |
|
} |
|
|
|
void LockBufPriv() |
|
{ |
|
MemCrit.lock(); |
|
if (sgdwLockCount != 0) { |
|
sgdwLockCount++; |
|
return; |
|
} |
|
|
|
sgdwLockCount++; |
|
} |
|
|
|
void UnlockBufPriv() |
|
{ |
|
if (sgdwLockCount == 0) |
|
app_fatal("draw main unlock error"); |
|
|
|
sgdwLockCount--; |
|
MemCrit.unlock(); |
|
} |
|
|
|
/** |
|
* @brief Limit FPS to avoid high CPU load, use when v-sync isn't available |
|
*/ |
|
void LimitFrameRate() |
|
{ |
|
if (!sgOptions.Graphics.bFPSLimit) |
|
return; |
|
static uint32_t frameDeadline; |
|
uint32_t tc = SDL_GetTicks() * 1000; |
|
uint32_t v = 0; |
|
if (frameDeadline > tc) { |
|
v = tc % refreshDelay; |
|
SDL_Delay(v / 1000 + 1); // ceil |
|
} |
|
frameDeadline = tc + v + refreshDelay; |
|
} |
|
|
|
} // namespace |
|
|
|
void dx_init() |
|
{ |
|
#ifndef USE_SDL1 |
|
SDL_RaiseWindow(ghMainWnd); |
|
SDL_ShowWindow(ghMainWnd); |
|
#endif |
|
|
|
CreatePrimarySurface(); |
|
palette_init(); |
|
CreateBackBuffer(); |
|
} |
|
|
|
void lock_buf(int idx) // NOLINT(misc-unused-parameters) |
|
{ |
|
#ifdef _DEBUG |
|
++locktbl[idx]; |
|
#endif |
|
LockBufPriv(); |
|
} |
|
|
|
void unlock_buf(int idx) // NOLINT(misc-unused-parameters) |
|
{ |
|
#ifdef _DEBUG |
|
if (locktbl[idx] == 0) |
|
app_fatal("Draw lock underflow: 0x%x", idx); |
|
--locktbl[idx]; |
|
#endif |
|
UnlockBufPriv(); |
|
} |
|
|
|
Surface GlobalBackBuffer() |
|
{ |
|
if (sgdwLockCount == 0) { |
|
Log("WARNING: Trying to obtain GlobalBackBuffer() without holding a lock"); |
|
return Surface(); |
|
} |
|
|
|
return Surface(PalSurface, SDL_Rect { 0, 0, gnScreenWidth, gnScreenHeight }); |
|
} |
|
|
|
void dx_cleanup() |
|
{ |
|
#ifndef USE_SDL1 |
|
if (ghMainWnd != nullptr) |
|
SDL_HideWindow(ghMainWnd); |
|
#endif |
|
MemCrit.lock(); |
|
sgdwLockCount = 0; |
|
MemCrit.unlock(); |
|
|
|
if (PalSurface == nullptr) |
|
return; |
|
PalSurface = nullptr; |
|
PinnedPalSurface = nullptr; |
|
Palette = nullptr; |
|
RendererTextureSurface = nullptr; |
|
#ifndef USE_SDL1 |
|
texture = nullptr; |
|
SDL_DestroyRenderer(renderer); |
|
#endif |
|
SDL_DestroyWindow(ghMainWnd); |
|
} |
|
|
|
void dx_reinit() |
|
{ |
|
#ifdef USE_SDL1 |
|
Uint32 flags = ghMainWnd->flags ^ SDL_FULLSCREEN; |
|
if (!IsFullScreen()) { |
|
flags |= SDL_FULLSCREEN; |
|
} |
|
ghMainWnd = SDL_SetVideoMode(0, 0, 0, flags); |
|
if (ghMainWnd == NULL) { |
|
ErrSdl(); |
|
} |
|
#else |
|
Uint32 flags = 0; |
|
if (!IsFullScreen()) { |
|
flags = renderer != nullptr ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN; |
|
} |
|
if (SDL_SetWindowFullscreen(ghMainWnd, flags) != 0) { |
|
ErrSdl(); |
|
} |
|
#endif |
|
force_redraw = 255; |
|
} |
|
|
|
void InitPalette() |
|
{ |
|
Palette = SDLWrap::AllocPalette(256); |
|
} |
|
|
|
void BltFast(SDL_Rect *srcRect, SDL_Rect *dstRect) |
|
{ |
|
if (RenderDirectlyToOutputSurface) |
|
return; |
|
Blit(PalSurface, srcRect, dstRect); |
|
} |
|
|
|
void Blit(SDL_Surface *src, SDL_Rect *srcRect, SDL_Rect *dstRect) |
|
{ |
|
SDL_Surface *dst = GetOutputSurface(); |
|
#ifndef USE_SDL1 |
|
if (SDL_BlitSurface(src, srcRect, dst, dstRect) < 0) |
|
ErrSdl(); |
|
#else |
|
if (!OutputRequiresScaling()) { |
|
if (SDL_BlitSurface(src, srcRect, dst, dstRect) < 0) |
|
ErrSdl(); |
|
return; |
|
} |
|
|
|
SDL_Rect scaledDstRect; |
|
if (dstRect != NULL) { |
|
scaledDstRect = *dstRect; |
|
ScaleOutputRect(&scaledDstRect); |
|
dstRect = &scaledDstRect; |
|
} |
|
|
|
// Same pixel format: We can call BlitScaled directly. |
|
if (SDLBackport_PixelFormatFormatEq(src->format, dst->format)) { |
|
if (SDL_BlitScaled(src, srcRect, dst, dstRect) < 0) |
|
ErrSdl(); |
|
return; |
|
} |
|
|
|
// If the surface has a color key, we must stretch first and can then call BlitSurface. |
|
if (SDL_HasColorKey(src)) { |
|
SDLSurfaceUniquePtr stretched = SDLWrap::CreateRGBSurface(SDL_SWSURFACE, dstRect->w, dstRect->h, src->format->BitsPerPixel, |
|
src->format->Rmask, src->format->Gmask, src->format->BitsPerPixel, src->format->Amask); |
|
SDL_SetColorKey(stretched.get(), SDL_SRCCOLORKEY, src->format->colorkey); |
|
if (src->format->palette != NULL) |
|
SDL_SetPalette(stretched.get(), SDL_LOGPAL, src->format->palette->colors, 0, src->format->palette->ncolors); |
|
SDL_Rect stretched_rect = { 0, 0, dstRect->w, dstRect->h }; |
|
if (SDL_SoftStretch(src, srcRect, stretched.get(), &stretched_rect) < 0 |
|
|| SDL_BlitSurface(stretched.get(), &stretched_rect, dst, dstRect) < 0) { |
|
ErrSdl(); |
|
} |
|
return; |
|
} |
|
|
|
// A surface with a non-output pixel format but without a color key needs scaling. |
|
// We can convert the format and then call BlitScaled. |
|
SDLSurfaceUniquePtr converted = SDLWrap::ConvertSurface(src, dst->format, 0); |
|
if (SDL_BlitScaled(converted.get(), srcRect, dst, dstRect) < 0) |
|
ErrSdl(); |
|
#endif |
|
} |
|
|
|
void RenderPresent() |
|
{ |
|
SDL_Surface *surface = GetOutputSurface(); |
|
|
|
if (!gbActive) { |
|
LimitFrameRate(); |
|
return; |
|
} |
|
|
|
#ifndef USE_SDL1 |
|
if (renderer != nullptr) { |
|
if (SDL_UpdateTexture(texture.get(), nullptr, surface->pixels, surface->pitch) <= -1) { //pitch is 2560 |
|
ErrSdl(); |
|
} |
|
|
|
// Clear buffer to avoid artifacts in case the window was resized |
|
if (SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255) <= -1) { // TODO only do this if window was resized |
|
ErrSdl(); |
|
} |
|
|
|
if (SDL_RenderClear(renderer) <= -1) { |
|
ErrSdl(); |
|
} |
|
if (SDL_RenderCopy(renderer, texture.get(), nullptr, nullptr) <= -1) { |
|
ErrSdl(); |
|
} |
|
SDL_RenderPresent(renderer); |
|
|
|
if (!sgOptions.Graphics.bVSync) { |
|
LimitFrameRate(); |
|
} |
|
} else { |
|
if (SDL_UpdateWindowSurface(ghMainWnd) <= -1) { |
|
ErrSdl(); |
|
} |
|
LimitFrameRate(); |
|
} |
|
#else |
|
if (SDL_Flip(surface) <= -1) { |
|
ErrSdl(); |
|
} |
|
if (RenderDirectlyToOutputSurface) |
|
PalSurface = GetOutputSurface(); |
|
LimitFrameRate(); |
|
#endif |
|
} |
|
|
|
void PaletteGetEntries(int dwNumEntries, SDL_Color *lpEntries) |
|
{ |
|
for (int i = 0; i < dwNumEntries; i++) { |
|
lpEntries[i] = system_palette[i]; |
|
} |
|
} |
|
} // namespace devilution
|
|
|