Add back 8bit rendering and also DOS support

This commit is contained in:
Anders Jenbo 2026-05-01 23:16:23 +02:00
parent c52a5204a4
commit 77402ef7ec
26 changed files with 1913 additions and 102 deletions

View File

@ -34,6 +34,12 @@ target_compile_definitions(miniaudio PUBLIC
MA_NO_RUNTIME_LINKING MA_NO_RUNTIME_LINKING
) )
if(DJGPP)
# DOS is single-threaded so we provide non-atomic __atomic_*_8 stubs
# (see CMakeLists.txt top-level comment about -march=i486).
target_sources(miniaudio PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/djgpp_atomic64.c")
endif()
if(DOWNLOAD_DEPENDENCIES) if(DOWNLOAD_DEPENDENCIES)
include(FetchContent) include(FetchContent)
FetchContent_Declare( FetchContent_Declare(

109
3rdparty/djgpp_atomic64.c vendored Normal file
View File

@ -0,0 +1,109 @@
/*
* Non-atomic 64-bit __atomic_*_8 stubs for DJGPP / DOS.
*
* DOS is single-threaded so real atomics are unnecessary. GCC emits calls to
* these helper functions when targeting i486 (or when __i586__ is undefined)
* because the ISA lacks a native 64-bit atomic instruction. Normally libatomic
* provides them, but DJGPP doesn't ship libatomic.
*
* Every function simply performs a plain (non-atomic) load/store/exchange/CAS
* which is perfectly safe in a single-threaded environment.
*/
#include <stdint.h>
#include <string.h>
uint64_t __atomic_load_8(const volatile void *ptr, int memorder)
{
(void)memorder;
uint64_t val;
memcpy(&val, (const void *)ptr, sizeof(val));
return val;
}
void __atomic_store_8(volatile void *ptr, uint64_t val, int memorder)
{
(void)memorder;
memcpy((void *)ptr, &val, sizeof(val));
}
uint64_t __atomic_exchange_8(volatile void *ptr, uint64_t val, int memorder)
{
(void)memorder;
uint64_t old;
memcpy(&old, (void *)ptr, sizeof(old));
memcpy((void *)ptr, &val, sizeof(val));
return old;
}
int __atomic_compare_exchange_8(
volatile void *ptr,
void *expected,
uint64_t desired,
int success_memorder,
int failure_memorder
)
{
(void)success_memorder;
(void)failure_memorder;
uint64_t current;
memcpy(&current, (void *)ptr, sizeof(current));
uint64_t exp;
memcpy(&exp, expected, sizeof(exp));
if (current == exp) {
memcpy((void *)ptr, &desired, sizeof(desired));
return 1;
}
memcpy(expected, &current, sizeof(current));
return 0;
}
uint64_t __atomic_fetch_add_8(volatile void *ptr, uint64_t val, int memorder)
{
(void)memorder;
uint64_t old;
memcpy(&old, (void *)ptr, sizeof(old));
uint64_t new_val = old + val;
memcpy((void *)ptr, &new_val, sizeof(new_val));
return old;
}
uint64_t __atomic_fetch_sub_8(volatile void *ptr, uint64_t val, int memorder)
{
(void)memorder;
uint64_t old;
memcpy(&old, (void *)ptr, sizeof(old));
uint64_t new_val = old - val;
memcpy((void *)ptr, &new_val, sizeof(new_val));
return old;
}
uint64_t __atomic_fetch_and_8(volatile void *ptr, uint64_t val, int memorder)
{
(void)memorder;
uint64_t old;
memcpy(&old, (void *)ptr, sizeof(old));
uint64_t new_val = old & val;
memcpy((void *)ptr, &new_val, sizeof(new_val));
return old;
}
uint64_t __atomic_fetch_or_8(volatile void *ptr, uint64_t val, int memorder)
{
(void)memorder;
uint64_t old;
memcpy(&old, (void *)ptr, sizeof(old));
uint64_t new_val = old | val;
memcpy((void *)ptr, &new_val, sizeof(new_val));
return old;
}
uint64_t __atomic_fetch_xor_8(volatile void *ptr, uint64_t val, int memorder)
{
(void)memorder;
uint64_t old;
memcpy(&old, (void *)ptr, sizeof(old));
uint64_t new_val = old ^ val;
memcpy((void *)ptr, &new_val, sizeof(new_val));
return old;
}

View File

@ -96,6 +96,15 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" CACHE PATH "Directory w
set(ISLE_EMSCRIPTEN_HOST "" CACHE STRING "Host URL for Emscripten streaming (e.g., https://test.com)") set(ISLE_EMSCRIPTEN_HOST "" CACHE STRING "Host URL for Emscripten streaming (e.g., https://test.com)")
cmake_dependent_option(BUILD_SHARED_LIBS "Build lego1 as a shared library" ON "NOT DOS;NOT EMSCRIPTEN;NOT VITA" OFF) cmake_dependent_option(BUILD_SHARED_LIBS "Build lego1 as a shared library" ON "NOT DOS;NOT EMSCRIPTEN;NOT VITA" OFF)
if(DOS)
# DJGPP targets i386 by default. We use i486 rather than i586 because i586
# enables cmpxchg8b which GCC uses for 64-bit atomics (lock cmpxchg8b)
# an instruction DOSBox does not support. The missing __atomic_*_8 helpers
# (normally in libatomic, which DJGPP doesn't ship) are provided as simple
# non-atomic stubs in 3rdparty/djgpp_atomic64.c since DOS is single-threaded.
add_compile_options(-march=i486)
endif()
message(STATUS "Isle app: ${ISLE_BUILD_APP}") message(STATUS "Isle app: ${ISLE_BUILD_APP}")
message(STATUS "Config app: ${ISLE_BUILD_CONFIG}") message(STATUS "Config app: ${ISLE_BUILD_CONFIG}")
message(STATUS "Internal DirectX5 SDK: ${ISLE_USE_DX5}") message(STATUS "Internal DirectX5 SDK: ${ISLE_USE_DX5}")

View File

@ -155,11 +155,19 @@ IsleApp::IsleApp()
m_using8bit = FALSE; m_using8bit = FALSE;
m_using16bit = TRUE; m_using16bit = TRUE;
m_hasLightSupport = FALSE; m_hasLightSupport = FALSE;
#ifdef __DJGPP__
m_drawCursor = TRUE;
#else
m_drawCursor = FALSE; m_drawCursor = FALSE;
#endif
m_use3dSound = TRUE; m_use3dSound = TRUE;
m_useMusic = TRUE; m_useMusic = TRUE;
m_wideViewAngle = TRUE; m_wideViewAngle = TRUE;
#ifdef __DJGPP__
m_islandQuality = 1;
#else
m_islandQuality = 2; m_islandQuality = 2;
#endif
m_islandTexture = 1; m_islandTexture = 1;
m_gameStarted = FALSE; m_gameStarted = FALSE;
m_frameDelta = 10; m_frameDelta = 10;
@ -191,14 +199,22 @@ IsleApp::IsleApp()
m_mediaPath = NULL; m_mediaPath = NULL;
m_iniPath = NULL; m_iniPath = NULL;
m_maxLod = RealtimeView::GetUserMaxLOD(); m_maxLod = RealtimeView::GetUserMaxLOD();
#ifdef __DJGPP__
m_maxLod = 1.0f;
#endif
m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20; m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20;
m_transitionType = MxTransitionManager::e_mosaic; m_transitionType = MxTransitionManager::e_mosaic;
m_cursorSensitivity = 4; m_cursorSensitivity = 4;
m_touchScheme = LegoInputManager::e_gamepad; m_touchScheme = LegoInputManager::e_gamepad;
m_haptic = TRUE; m_haptic = TRUE;
m_wasd = FALSE; m_wasd = FALSE;
#ifdef __DJGPP__
m_xRes = 320;
m_yRes = 200;
#else
m_xRes = 640; m_xRes = 640;
m_yRes = 480; m_yRes = 480;
#endif
m_exclusiveXRes = m_xRes; m_exclusiveXRes = m_xRes;
m_exclusiveYRes = m_yRes; m_exclusiveYRes = m_yRes;
m_exclusiveFrameRate = 60.00f; m_exclusiveFrameRate = 60.00f;
@ -243,6 +259,10 @@ void IsleApp::Close()
TransitionManager()->SetWaitIndicator(NULL); TransitionManager()->SetWaitIndicator(NULL);
Lego()->Resume(); Lego()->Resume();
if (BackgroundAudioManager()) {
BackgroundAudioManager()->Stop();
}
while (Streamer()->Close(NULL) == SUCCESS) { while (Streamer()->Close(NULL) == SUCCESS) {
} }
@ -324,8 +344,16 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0"); SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0"); SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#ifdef __DJGPP__
SDL_SetHint("SDL_DOS_ALLOW_DIRECT_FRAMEBUFFER", "1");
#endif
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC)) { Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD;
#ifndef __DJGPP__
initFlags |= SDL_INIT_HAPTIC;
#endif
if (!SDL_Init(initFlags)) {
char buffer[256]; char buffer[256];
SDL_snprintf( SDL_snprintf(
buffer, buffer,
@ -717,11 +745,17 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
g_lastMouseX = event->motion.x; g_lastMouseX = event->motion.x;
g_lastMouseY = event->motion.y; g_lastMouseY = event->motion.y;
#ifdef __DJGPP__
if (VideoManager()) {
VideoManager()->MoveCursor(Min((MxS32) g_lastMouseX, 639), Min((MxS32) g_lastMouseY, 479));
}
#else
SDL_ShowCursor(); SDL_ShowCursor();
g_isle->SetDrawCursor(FALSE); g_isle->SetDrawCursor(FALSE);
if (VideoManager()) { if (VideoManager()) {
VideoManager()->SetCursorBitmap(NULL); VideoManager()->SetCursorBitmap(NULL);
} }
#endif
break; break;
case SDL_EVENT_FINGER_MOTION: { case SDL_EVENT_FINGER_MOTION: {
g_mousemoved = TRUE; g_mousemoved = TRUE;
@ -924,6 +958,22 @@ MxResult IsleApp::SetupWindow()
return FAILURE; return FAILURE;
} }
#if defined(MINIWIN)
// MINIWIN: window/VESA mode matches the game's rendering resolution.
g_targetWidth = m_xRes;
g_targetHeight = m_yRes;
#else
// DX5: fullscreen uses exclusive resolution for display mode switching.
if (m_fullScreen) {
g_targetWidth = m_exclusiveXRes;
g_targetHeight = m_exclusiveYRes;
}
else {
g_targetWidth = m_xRes;
g_targetHeight = m_yRes;
}
#endif
SetupVideoFlags( SetupVideoFlags(
m_fullScreen, m_fullScreen,
m_flipSurfaces, m_flipSurfaces,
@ -954,7 +1004,7 @@ MxResult IsleApp::SetupWindow()
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight); SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen); SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen);
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE); SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE);
#if defined(MINIWIN) && !defined(__3DS__) && !defined(WINDOWS_STORE) && !defined(__vita__) #if defined(MINIWIN) && !defined(__3DS__) && !defined(WINDOWS_STORE) && !defined(__vita__) && !defined(__DJGPP__)
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true); SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
@ -969,6 +1019,17 @@ MxResult IsleApp::SetupWindow()
SDL_SetPointerProperty(SDL_GetWindowProperties(window), ISLE_PROP_WINDOW_CREATE_VIDEO_PARAM, &m_videoParam); SDL_SetPointerProperty(SDL_GetWindowProperties(window), ISLE_PROP_WINDOW_CREATE_VIDEO_PARAM, &m_videoParam);
#ifdef __DJGPP__
// DOS: request an 8-bit (INDEX8) fullscreen mode so the VESA
// framebuffer is paletted and we can blit INDEX8 surfaces directly.
{
SDL_DisplayMode mode = {};
mode.w = g_targetWidth;
mode.h = g_targetHeight;
mode.format = SDL_PIXELFORMAT_INDEX8;
SDL_SetWindowFullscreenMode(window, &mode);
}
#else
if (m_exclusiveFullScreen && m_fullScreen) { if (m_exclusiveFullScreen && m_fullScreen) {
SDL_DisplayMode closestMode; SDL_DisplayMode closestMode;
SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); SDL_DisplayID displayID = SDL_GetDisplayForWindow(window);
@ -983,6 +1044,7 @@ MxResult IsleApp::SetupWindow()
SDL_SetWindowFullscreenMode(window, &closestMode); SDL_SetWindowFullscreenMode(window, &closestMode);
} }
} }
#endif
#ifdef MINIWIN #ifdef MINIWIN
m_windowHandle = reinterpret_cast<HWND>(window); m_windowHandle = reinterpret_cast<HWND>(window);

View File

@ -76,6 +76,7 @@ class LegoVideoManager : public MxVideoManager {
void SetRender3D(MxBool p_render3d) { m_render3d = p_render3d; } void SetRender3D(MxBool p_render3d) { m_render3d = p_render3d; }
void SetUnk0x554(MxBool p_unk0x554) { m_unk0x554 = p_unk0x554; } void SetUnk0x554(MxBool p_unk0x554) { m_unk0x554 = p_unk0x554; }
MxBool GetDrawCursor() { return m_drawCursor; }
// SYNTHETIC: LEGO1 0x1007ab20 // SYNTHETIC: LEGO1 0x1007ab20
// SYNTHETIC: BETA10 0x100d8040 // SYNTHETIC: BETA10 0x100d8040
@ -88,9 +89,6 @@ class LegoVideoManager : public MxVideoManager {
inline void DrawCursor(); inline void DrawCursor();
void DrawDigitToBuffer32(uint8_t* p_dst, int p_pitch, int p_x, int p_y, int p_digit, uint32_t p_color);
void DrawTextToSurface32(uint8_t* p_dst, int p_pitch, int p_x, int p_y, const char* p_text, uint32_t p_color);
Tgl::Renderer* m_renderer; // 0x64 Tgl::Renderer* m_renderer; // 0x64
Lego3DManager* m_3dManager; // 0x68 Lego3DManager* m_3dManager; // 0x68
LegoROI* m_viewROI; // 0x6c LegoROI* m_viewROI; // 0x6c

View File

@ -49,7 +49,7 @@ class MxBackgroundAudioManager : public MxCore {
void Init(); void Init();
void Update(MxS32 p_targetVolume, MxS32 p_speed, MxPresenter::TickleState p_tickleState); void Update(MxS32 p_targetVolume, MxS32 p_speed, MxPresenter::TickleState p_tickleState);
void Stop(); LEGO1_EXPORT void Stop();
void LowerVolume(); void LowerVolume();
void RaiseVolume(); void RaiseVolume();
MxResult SetPendingPresenter(MxPresenter* p_presenter, MxS32 p_speed, MxPresenter::TickleState p_tickleState); MxResult SetPendingPresenter(MxPresenter* p_presenter, MxS32 p_speed, MxPresenter::TickleState p_tickleState);

View File

@ -77,6 +77,10 @@ void MxBackgroundAudioManager::DestroyMusic()
Streamer()->Close(m_script.GetInternal()); Streamer()->Close(m_script.GetInternal());
m_enabled = FALSE; m_enabled = FALSE;
} }
m_activePresenter = NULL;
m_pendingPresenter = NULL;
m_tickleState = MxPresenter::e_idle;
} }
// FUNCTION: LEGO1 0x1007ee40 // FUNCTION: LEGO1 0x1007ee40

View File

@ -465,7 +465,45 @@ void LegoVideoManager::DrawFPS()
if (m_unk0x528->Lock(NULL, &surfaceDesc, DDLOCK_WAIT, NULL) == DD_OK) { if (m_unk0x528->Lock(NULL, &surfaceDesc, DDLOCK_WAIT, NULL) == DD_OK) {
memset(surfaceDesc.lpSurface, 0, surfaceDesc.lPitch * surfaceDesc.dwHeight); memset(surfaceDesc.lpSurface, 0, surfaceDesc.lPitch * surfaceDesc.dwHeight);
DrawTextToSurface32((uint8_t*) surfaceDesc.lpSurface, surfaceDesc.lPitch, 0, 0, buffer, 0xFF0000FF); // 8-bit bitmap font for FPS display
uint8_t* dst = (uint8_t*) surfaceDesc.lpSurface;
int pitch = surfaceDesc.lPitch;
const char* p = buffer;
int px = 0;
static const uint8_t g_digitFont[5][10] = {
{0b1111, 0b0001, 0b1111, 0b1111, 0b1001, 0b1111, 0b1111, 0b1111, 0b1111, 0b1111},
{0b1001, 0b0001, 0b0001, 0b0001, 0b1001, 0b1000, 0b1000, 0b0001, 0b1001, 0b1001},
{0b1001, 0b0001, 0b1111, 0b1111, 0b1111, 0b1111, 0b1111, 0b0010, 0b1111, 0b1111},
{0b1001, 0b0001, 0b1000, 0b0001, 0b0001, 0b0001, 0b1001, 0b0010, 0b1001, 0b0001},
{0b1111, 0b0001, 0b1111, 0b1111, 0b0001, 0b1111, 0b1111, 0b0100, 0b1111, 0b1111},
};
while (*p) {
if (*p >= '0' && *p <= '9') {
int d = *p - '0';
for (int row = 0; row < 5; ++row) {
uint8_t bits = g_digitFont[row][d];
for (int col = 0; col < 5; ++col) {
if (bits & (1 << (4 - col))) {
for (int dy = 0; dy < 2; ++dy) {
for (int dx = 0; dx < 2; ++dx) {
dst[(row * 2 + dy) * pitch + (px + col * 2 + dx)] = 0xff;
}
}
}
}
}
px += 10;
}
else if (*p == '.') {
for (int dy = 0; dy < 2; ++dy) {
for (int dx = 0; dx < 2; ++dx) {
dst[(10 + dy) * pitch + (px + 2 + dx)] = 0xff;
}
}
px += 4;
}
++p;
}
m_unk0x528->Unlock(surfaceDesc.lpSurface); m_unk0x528->Unlock(surfaceDesc.lpSurface);
m_unk0x550 = 1.f; m_unk0x550 = 1.f;
@ -789,66 +827,6 @@ MxResult LegoVideoManager::ConfigureD3DRM()
return SUCCESS; return SUCCESS;
} }
void LegoVideoManager::DrawDigitToBuffer32(uint8_t* p_dst, int p_pitch, int p_x, int p_y, int p_digit, uint32_t p_color)
{
if (p_digit < 0 || p_digit > 9) {
return;
}
uint32_t* pixels = (uint32_t*) p_dst;
int rowStride = p_pitch / 4;
// 4x5 bitmap font
const uint8_t digitFont[5][10] = {
{0b1111, 0b0001, 0b1111, 0b1111, 0b1001, 0b1111, 0b1111, 0b1111, 0b1111, 0b1111},
{0b1001, 0b0001, 0b0001, 0b0001, 0b1001, 0b1000, 0b1000, 0b0001, 0b1001, 0b1001},
{0b1001, 0b0001, 0b1111, 0b1111, 0b1111, 0b1111, 0b1111, 0b0010, 0b1111, 0b1111},
{0b1001, 0b0001, 0b1000, 0b0001, 0b0001, 0b0001, 0b1001, 0b0010, 0b1001, 0b0001},
{0b1111, 0b0001, 0b1111, 0b1111, 0b0001, 0b1111, 0b1111, 0b0100, 0b1111, 0b1111},
};
for (int row = 0; row < 5; ++row) {
uint8_t bits = digitFont[row][p_digit];
for (int col = 0; col < 5; ++col) {
if (bits & (1 << (4 - col))) {
for (int dy = 0; dy < 2; ++dy) {
for (int dx = 0; dx < 2; ++dx) {
pixels[(p_y + row * 2 + dy) * rowStride + (p_x + col * 2 + dx)] = p_color;
}
}
}
}
}
}
void LegoVideoManager::DrawTextToSurface32(
uint8_t* p_dst,
int p_pitch,
int p_x,
int p_y,
const char* p_text,
uint32_t p_color
)
{
while (*p_text) {
if (*p_text >= '0' && *p_text <= '9') {
DrawDigitToBuffer32(p_dst, p_pitch, p_x, p_y, *p_text - '0', p_color);
p_x += 10;
}
else if (*p_text == '.') {
uint32_t* pixels = (uint32_t*) p_dst;
int rowStride = p_pitch / 4;
for (int dy = 0; dy < 2; ++dy) {
for (int dx = 0; dx < 2; ++dx) {
pixels[(p_y + 10 + dy) * rowStride + (p_x + 2 + dx)] = p_color;
}
}
p_x += 4;
}
++p_text;
}
}
void LegoVideoManager::SetCursorBitmap(const CursorBitmap* p_cursorBitmap) void LegoVideoManager::SetCursorBitmap(const CursorBitmap* p_cursorBitmap)
{ {
if (p_cursorBitmap == NULL) { if (p_cursorBitmap == NULL) {

View File

@ -1499,7 +1499,7 @@ void Infocenter::StartCredits()
GetViewManager()->RemoveAll(NULL); GetViewManager()->RemoveAll(NULL);
InvokeAction(Extra::e_opendisk, *g_creditsScript, CreditsScript::c_LegoCredits, NULL); InvokeAction(Extra::e_opendisk, *g_creditsScript, CreditsScript::c_LegoCredits, NULL);
SetAppCursor(e_cursorArrow); SetAppCursor(VideoManager()->GetDrawCursor() ? e_cursorNone : e_cursorArrow);
} }
// FUNCTION: LEGO1 0x10071250 // FUNCTION: LEGO1 0x10071250

View File

@ -305,6 +305,7 @@ void MxDisplaySurface::Destroy()
// FUNCTION: BETA10 0x1013fe15 // FUNCTION: BETA10 0x1013fe15
void MxDisplaySurface::SetPalette(MxPalette* p_palette) void MxDisplaySurface::SetPalette(MxPalette* p_palette)
{ {
#ifndef MINIWIN
if ((m_surfaceDesc.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) == DDPF_PALETTEINDEXED8) { if ((m_surfaceDesc.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) == DDPF_PALETTEINDEXED8) {
m_ddSurface1->SetPalette(p_palette->CreateNativePalette()); m_ddSurface1->SetPalette(p_palette->CreateNativePalette());
m_ddSurface2->SetPalette(p_palette->CreateNativePalette()); m_ddSurface2->SetPalette(p_palette->CreateNativePalette());
@ -326,8 +327,10 @@ void MxDisplaySurface::SetPalette(MxPalette* p_palette)
DeleteObject(hpal); DeleteObject(hpal);
} }
} }
#else
m_ddSurface1->SetPalette(p_palette->CreateNativePalette());
m_ddSurface2->SetPalette(p_palette->CreateNativePalette());
#ifndef MINIWIN
MxS32 bitCount = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount; MxS32 bitCount = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount;
if (bitCount == 8) { if (bitCount == 8) {
return; return;
@ -449,17 +452,6 @@ void MxDisplaySurface::VTable0x28(
} }
#endif #endif
if (m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount != 32) {
DDCOLORKEY colorKey;
if (m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount == 8) {
colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = 0x10;
}
else {
colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f);
}
tempSurface->SetColorKey(DDCKEY_SRCBLT, &colorKey);
}
DDSURFACEDESC tempDesc; DDSURFACEDESC tempDesc;
memset(&tempDesc, 0, sizeof(tempDesc)); memset(&tempDesc, 0, sizeof(tempDesc));
tempDesc.dwSize = sizeof(tempDesc); tempDesc.dwSize = sizeof(tempDesc);
@ -511,10 +503,10 @@ void MxDisplaySurface::VTable0x28(
if (m_videoParam.Flags().GetDoubleScaling()) { if (m_videoParam.Flags().GetDoubleScaling()) {
RECT destRect = {p_right, p_bottom, p_right + p_width * 2, p_bottom + p_height * 2}; RECT destRect = {p_right, p_bottom, p_right + p_width * 2, p_bottom + p_height * 2};
m_ddSurface2->Blt(&destRect, tempSurface, NULL, DDBLT_WAIT | DDBLT_KEYSRC, NULL); m_ddSurface2->Blt(&destRect, tempSurface, NULL, DDBLT_WAIT, NULL);
} }
else { else {
m_ddSurface2->BltFast(p_right, p_bottom, tempSurface, NULL, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY); m_ddSurface2->BltFast(p_right, p_bottom, tempSurface, NULL, DDBLTFAST_WAIT);
} }
tempSurface->Release(); tempSurface->Release();
@ -1083,10 +1075,6 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::FUN_100bc8b0(MxS32 p_width, MxS32 p_height
return NULL; return NULL;
} }
if (surfaceDesc.ddpfPixelFormat.dwRGBBitCount == 8) {
return NULL;
}
surfaceDesc.dwWidth = p_width; surfaceDesc.dwWidth = p_width;
surfaceDesc.dwHeight = p_height; surfaceDesc.dwHeight = p_height;
surfaceDesc.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; surfaceDesc.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;

View File

@ -5,7 +5,6 @@
#include <math.h> #include <math.h>
#include <memory.h> #include <memory.h>
#include <string.h>
// FUNCTION: LEGO1 0x10001f80 // FUNCTION: LEGO1 0x10001f80
// FUNCTION: BETA10 0x10010a20 // FUNCTION: BETA10 0x10010a20

View File

@ -5,7 +5,6 @@
#include <math.h> #include <math.h>
#include <memory.h> #include <memory.h>
#include <string.h>
// FUNCTION: LEGO1 0x10002870 // FUNCTION: LEGO1 0x10002870
// FUNCTION: BETA10 0x10048500 // FUNCTION: BETA10 0x10048500

View File

@ -238,7 +238,9 @@ inline void ViewManager::ManageVisibilityAndDetailRecursively(ViewROI* p_from, i
const CompoundObject* comp = p_from->GetComp(); const CompoundObject* comp = p_from->GetComp();
if (p_lodLevel == ViewROI::c_lodLevelUnset) { if (p_lodLevel == ViewROI::c_lodLevelUnset) {
if (p_from->GetWorldBoundingSphere().Radius() > 0.001F) { // FIX: Use 0.002 threshold to avoid x87 extended precision boundary
// issues where 0.001 sentinel radius compares as > 0.001F on x87.
if (p_from->GetWorldBoundingSphere().Radius() > 0.002F) {
float projectedSize = ProjectedSize(p_from->GetWorldBoundingSphere()); float projectedSize = ProjectedSize(p_from->GetWorldBoundingSphere());
if (RealtimeView::GetUserMaxLOD() <= 5.0f && projectedSize < seconds_allowed * g_viewDistance) { if (RealtimeView::GetUserMaxLOD() <= 5.0f && projectedSize < seconds_allowed * g_viewDistance) {

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <cstdlib>
#include <map> #include <map>
#include <vector> #include <vector>

View File

@ -73,6 +73,8 @@ if(DOS)
list(REMOVE_ITEM GRAPHICS_BACKENDS USE_SDL_GPU USE_OPENGL1 USE_OPENGLES2) #USE_SDL_GPU list(REMOVE_ITEM GRAPHICS_BACKENDS USE_SDL_GPU USE_OPENGL1 USE_OPENGLES2) #USE_SDL_GPU
endif() endif()
list(APPEND GRAPHICS_BACKENDS USE_PALETTE_SW_RENDER)
if(NINTENDO_SWITCH) if(NINTENDO_SWITCH)
# Remove USE_OPENGL1 as incompatible. # Remove USE_OPENGL1 as incompatible.
# Remove everything else as not needed. # Remove everything else as not needed.
@ -143,6 +145,12 @@ if(USE_SOFTWARE_RENDER IN_LIST GRAPHICS_BACKENDS)
) )
endif() endif()
if(USE_PALETTE_SW_RENDER IN_LIST GRAPHICS_BACKENDS)
target_sources(miniwin PRIVATE
src/d3drm/backends/palettesw/renderer.cpp
)
endif()
target_compile_definitions(miniwin PUBLIC MINIWIN) target_compile_definitions(miniwin PUBLIC MINIWIN)
target_include_directories(miniwin target_include_directories(miniwin

File diff suppressed because it is too large Load Diff

View File

@ -826,6 +826,13 @@ void Direct3DRMSoftwareRenderer::SetDither(bool dither)
{ {
} }
void Direct3DRMSoftwareRenderer::SetPalette(SDL_Palette* palette)
{
if (m_renderedImage) {
SDL_SetSurfacePalette(m_renderedImage, palette);
}
}
void Direct3DRMSoftwareRenderer::Download(SDL_Surface* target) void Direct3DRMSoftwareRenderer::Download(SDL_Surface* target)
{ {
SDL_Rect srcRect = { SDL_Rect srcRect = {

View File

@ -20,6 +20,9 @@
#ifdef USE_SOFTWARE_RENDER #ifdef USE_SOFTWARE_RENDER
#include "d3drmrenderer_software.h" #include "d3drmrenderer_software.h"
#endif #endif
#ifdef USE_PALETTE_SW_RENDER
#include "d3drmrenderer_palettesw.h"
#endif
#ifdef USE_GXM #ifdef USE_GXM
#include "d3drmrenderer_gxm.h" #include "d3drmrenderer_gxm.h"
#endif #endif
@ -74,6 +77,11 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer(
if (SDL_memcmp(guid, &GXM_GUID, sizeof(GUID)) == 0) { if (SDL_memcmp(guid, &GXM_GUID, sizeof(GUID)) == 0) {
return GXMRenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetMSAASamples()); return GXMRenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetMSAASamples());
} }
#endif
#ifdef USE_PALETTE_SW_RENDER
if (SDL_memcmp(guid, &PALETTE_SW_GUID, sizeof(GUID)) == 0) {
return new Direct3DRMPaletteSWRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight);
}
#endif #endif
return nullptr; return nullptr;
} }
@ -101,6 +109,9 @@ void Direct3DRMRenderer_EnumDevices(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICE
#ifdef USE_SOFTWARE_RENDER #ifdef USE_SOFTWARE_RENDER
Direct3DRMSoftware_EnumDevice(cb, ctx); Direct3DRMSoftware_EnumDevice(cb, ctx);
#endif #endif
#ifdef USE_PALETTE_SW_RENDER
Direct3DRMPaletteSW_EnumDevice(cb, ctx);
#endif
#ifdef USE_GXM #ifdef USE_GXM
GXMRenderer_EnumDevice(cb, ctx); GXMRenderer_EnumDevice(cb, ctx);
#endif #endif

View File

@ -245,7 +245,12 @@ HRESULT DirectDrawImpl::GetDisplayMode(LPDDSURFACEDESC lpDDSurfaceDesc)
#ifdef MINIWIN_PIXELFORMAT #ifdef MINIWIN_PIXELFORMAT
format = MINIWIN_PIXELFORMAT; format = MINIWIN_PIXELFORMAT;
#else #else
if (m_virtualBPP == 8 || (m_frameBuffer && m_frameBuffer->IsIndex8())) {
format = SDL_PIXELFORMAT_INDEX8;
}
else {
format = mode->format; format = mode->format;
}
#endif #endif
const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(format); const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(format);
@ -308,6 +313,7 @@ HRESULT DirectDrawImpl::SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBP
{ {
m_virtualWidth = dwWidth; m_virtualWidth = dwWidth;
m_virtualHeight = dwHeight; m_virtualHeight = dwHeight;
m_virtualBPP = dwBPP;
return DD_OK; return DD_OK;
} }

View File

@ -53,13 +53,20 @@ HRESULT DirectDrawSurfaceImpl::Blt(
) )
{ {
if ((dwFlags & DDBLT_COLORFILL) == DDBLT_COLORFILL) { if ((dwFlags & DDBLT_COLORFILL) == DDBLT_COLORFILL) {
Uint32 color;
if (m_surface->format == SDL_PIXELFORMAT_INDEX8) {
// For INDEX8 surfaces the fill color is a palette index, not RGBA.
color = lpDDBltFx->dwFillColor & 0xFF;
}
else {
Uint8 a = (lpDDBltFx->dwFillColor >> 24) & 0xFF; Uint8 a = (lpDDBltFx->dwFillColor >> 24) & 0xFF;
Uint8 r = (lpDDBltFx->dwFillColor >> 16) & 0xFF; Uint8 r = (lpDDBltFx->dwFillColor >> 16) & 0xFF;
Uint8 g = (lpDDBltFx->dwFillColor >> 8) & 0xFF; Uint8 g = (lpDDBltFx->dwFillColor >> 8) & 0xFF;
Uint8 b = lpDDBltFx->dwFillColor & 0xFF; Uint8 b = lpDDBltFx->dwFillColor & 0xFF;
const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_surface->format); const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_surface->format);
Uint32 color = SDL_MapRGBA(details, nullptr, r, g, b, a); color = SDL_MapRGBA(details, nullptr, r, g, b, a);
}
if (lpDestRect) { if (lpDestRect) {
SDL_Rect dstRect = ConvertRect(lpDestRect); SDL_Rect dstRect = ConvertRect(lpDestRect);
SDL_FillSurfaceRect(m_surface, &dstRect, color); SDL_FillSurfaceRect(m_surface, &dstRect, color);

View File

@ -9,7 +9,11 @@
FrameBufferImpl::FrameBufferImpl(DWORD virtualWidth, DWORD virtualHeight) FrameBufferImpl::FrameBufferImpl(DWORD virtualWidth, DWORD virtualHeight)
: m_virtualWidth(virtualWidth), m_virtualHeight(virtualHeight) : m_virtualWidth(virtualWidth), m_virtualHeight(virtualHeight)
{ {
#ifdef __DJGPP__
m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_INDEX8);
#else
m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_RGBA32); m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_RGBA32);
#endif
} }
FrameBufferImpl::~FrameBufferImpl() FrameBufferImpl::~FrameBufferImpl()
@ -49,7 +53,7 @@ HRESULT FrameBufferImpl::Blt(
return DDERR_GENERIC; return DDERR_GENERIC;
} }
if (dynamic_cast<FrameBufferImpl*>(lpDDSrcSurface) == this) { if (dynamic_cast<FrameBufferImpl*>(lpDDSrcSurface)) {
return Flip(nullptr, DDFLIP_WAIT); return Flip(nullptr, DDFLIP_WAIT);
} }
@ -103,7 +107,11 @@ HRESULT FrameBufferImpl::BltFast(
int height = lpSrcRect ? (lpSrcRect->bottom - lpSrcRect->top) : surface->m_surface->h; int height = lpSrcRect ? (lpSrcRect->bottom - lpSrcRect->top) : surface->m_surface->h;
RECT destRect = {(int) dwX, (int) dwY, (int) (dwX + width), (int) (dwY + height)}; RECT destRect = {(int) dwX, (int) dwY, (int) (dwX + width), (int) (dwY + height)};
return Blt(&destRect, lpDDSrcSurface, lpSrcRect, DDBLT_NONE, nullptr); DDBltFlags flags = DDBLT_NONE;
if ((dwTrans & DDBLTFAST_SRCCOLORKEY) == DDBLTFAST_SRCCOLORKEY) {
flags = flags | DDBLT_KEYSRC;
}
return Blt(&destRect, lpDDSrcSurface, lpSrcRect, flags, nullptr);
} }
HRESULT FrameBufferImpl::Flip(LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride, DDFlipFlags dwFlags) HRESULT FrameBufferImpl::Flip(LPDIRECTDRAWSURFACE lpDDSurfaceTargetOverride, DDFlipFlags dwFlags)
@ -210,8 +218,13 @@ HRESULT FrameBufferImpl::SetColorKey(DDColorKeyFlags dwFlags, LPDDCOLORKEY lpDDC
HRESULT FrameBufferImpl::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) HRESULT FrameBufferImpl::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette)
{ {
if (m_transferBuffer->m_surface->format != SDL_PIXELFORMAT_INDEX8) { // If the transfer buffer is not INDEX8 yet, recreate it — but only when
MINIWIN_NOT_IMPLEMENTED(); // the renderer actually works with paletted surfaces (palette SW / DOS).
// GL-based renderers use RGBA32 transfer buffers and convert on upload.
if (m_transferBuffer->m_surface->format != SDL_PIXELFORMAT_INDEX8 && DDRenderer &&
DDRenderer->UsesPalettedSurfaces()) {
m_transferBuffer->Release();
m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_INDEX8);
} }
lpDDPalette->AddRef(); lpDDPalette->AddRef();
@ -222,6 +235,11 @@ HRESULT FrameBufferImpl::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette)
m_palette = lpDDPalette; m_palette = lpDDPalette;
SDL_SetSurfacePalette(m_transferBuffer->m_surface, ((DirectDrawPaletteImpl*) m_palette)->m_palette); SDL_SetSurfacePalette(m_transferBuffer->m_surface, ((DirectDrawPaletteImpl*) m_palette)->m_palette);
if (DDRenderer) {
DDRenderer->SetPalette(((DirectDrawPaletteImpl*) m_palette)->m_palette);
}
return DD_OK; return DD_OK;
} }

View File

@ -55,6 +55,8 @@ class Direct3DRMRenderer : public IDirect3DDevice2 {
virtual void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) = 0; virtual void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) = 0;
virtual void Download(SDL_Surface* target) = 0; virtual void Download(SDL_Surface* target) = 0;
virtual void SetDither(bool dither) = 0; virtual void SetDither(bool dither) = 0;
virtual void SetPalette(SDL_Palette* palette) {}
virtual bool UsesPalettedSurfaces() const { return false; }
protected: protected:
int m_width, m_height; int m_width, m_height;

View File

@ -0,0 +1,116 @@
#pragma once
#include "d3drmrenderer.h"
#include "d3drmtexture_impl.h"
#include "ddraw_impl.h"
#include <SDL3/SDL.h>
#include <cstddef>
#include <vector>
DEFINE_GUID(PALETTE_SW_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07);
struct PaletteTextureCache {
Direct3DRMTextureImpl* texture;
Uint8 version;
SDL_Surface* cached;
};
struct PaletteMeshCache {
const MeshGroup* meshGroup;
int version;
bool flat;
std::vector<D3DRMVERTEX> vertices;
std::vector<uint16_t> indices;
};
class Direct3DRMPaletteSWRenderer : public Direct3DRMRenderer {
public:
Direct3DRMPaletteSWRenderer(DWORD width, DWORD height);
~Direct3DRMPaletteSWRenderer() override;
void PushLights(const SceneLight* vertices, size_t count) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override;
Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override;
void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override;
void SetFrustumPlanes(const Plane* frustumPlanes) override;
HRESULT BeginFrame() override;
void EnableTransparency() override;
void SubmitDraw(
DWORD meshId,
const D3DRMMATRIX4D& modelViewMatrix,
const D3DRMMATRIX4D& worldMatrix,
const D3DRMMATRIX4D& viewMatrix,
const Matrix3x3& normalMatrix,
const Appearance& appearance
) override;
HRESULT FinalizeFrame() override;
void Resize(int width, int height, const ViewportTransform& viewportTransform) override;
void Clear(float r, float g, float b) override;
void Flip() override;
void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override;
void Download(SDL_Surface* target) override;
void SetDither(bool dither) override;
void SetPalette(SDL_Palette* palette) override;
bool UsesPalettedSurfaces() const override { return true; }
private:
void ClearZBuffer();
void DrawTriangleProjected(
const D3DRMVERTEX& v0,
const D3DRMVERTEX& v1,
const D3DRMVERTEX& v2,
const Appearance& appearance
);
void DrawTriangleClipped(const D3DRMVERTEX (&v)[3], const Appearance& appearance);
void ProjectVertex(const D3DVECTOR& v, D3DRMVECTOR4D& p) const;
Uint8 ApplyLighting(const D3DVECTOR& position, const D3DVECTOR& normal, const Appearance& appearance, Uint8 texel);
void BuildLightingLUT();
void BuildBlendLUT();
void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture);
void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh);
SDL_Surface* m_renderedImage = nullptr;
SDL_Palette* m_palette = nullptr;
SDL_Palette* m_flipPalette = nullptr; // Palette snapshot taken at Flip time (the correct one)
bool m_flipPaletteDirty = false;
std::vector<SceneLight> m_lights;
std::vector<PaletteTextureCache> m_textures;
std::vector<PaletteMeshCache> m_meshes;
D3DVALUE m_front;
D3DVALUE m_back;
Matrix3x3 m_normalMatrix;
D3DRMMATRIX4D m_projection;
std::vector<float> m_zBuffer;
std::vector<D3DRMVERTEX> m_transformedVerts;
Plane m_frustumPlanes[6];
// Lighting LUT: for each of 256 palette entries x 32 brightness levels,
// store the best-matching palette index.
// Usage: m_lightLUT[paletteIndex * 32 + brightnessLevel]
static constexpr int LIGHT_LEVELS = 32;
Uint8 m_lightLUT[256 * LIGHT_LEVELS];
// Blend LUT: for any two palette indices, the pre-computed 50/50 blend
// result mapped to the nearest palette colour.
// Usage: m_blendLUT[srcIndex * 256 + dstIndex]
Uint8 m_blendLUT[256 * 256];
bool m_lightLUTDirty = true;
bool m_transparencyEnabled = false;
};
inline static void Direct3DRMPaletteSW_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
{
D3DDEVICEDESC halDesc = {};
D3DDEVICEDESC helDesc = {};
helDesc.dcmColorModel = D3DCOLOR_RGB;
helDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
helDesc.dwDeviceZBufferBitDepth = DDBD_16;
helDesc.dwDeviceRenderBitDepth = DDBD_8;
helDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
helDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
helDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
EnumDevice(cb, ctx, "Miniwin Paletted Software", &halDesc, &helDesc, PALETTE_SW_GUID);
}

View File

@ -50,6 +50,7 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer {
void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override;
void Download(SDL_Surface* target) override; void Download(SDL_Surface* target) override;
void SetDither(bool dither) override; void SetDither(bool dither) override;
void SetPalette(SDL_Palette* palette) override;
private: private:
void ClearZBuffer(); void ClearZBuffer();

View File

@ -61,9 +61,10 @@ struct DirectDrawImpl : public IDirectDraw2, public IDirect3D2, public IDirect3D
float GetAnisotropic() const override { return m_anisotropic; } float GetAnisotropic() const override { return m_anisotropic; }
private: private:
FrameBufferImpl* m_frameBuffer; FrameBufferImpl* m_frameBuffer = nullptr;
int m_virtualWidth = 0; int m_virtualWidth = 0;
int m_virtualHeight = 0; int m_virtualHeight = 0;
int m_virtualBPP = 0;
DWORD m_msaaSamples = 0; DWORD m_msaaSamples = 0;
float m_anisotropic = 0.0f; float m_anisotropic = 0.0f;
}; };

View File

@ -36,6 +36,8 @@ struct FrameBufferImpl : public IDirectDrawSurface3 {
HRESULT SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) override; HRESULT SetPalette(LPDIRECTDRAWPALETTE lpDDPalette) override;
HRESULT Unlock(LPVOID lpSurfaceData) override; HRESULT Unlock(LPVOID lpSurfaceData) override;
bool IsIndex8() const { return m_transferBuffer->m_surface->format == SDL_PIXELFORMAT_INDEX8; }
private: private:
uint32_t m_virtualWidth; uint32_t m_virtualWidth;
uint32_t m_virtualHeight; uint32_t m_virtualHeight;