This commit is contained in:
Anders Jenbo 2026-05-01 23:33:48 +02:00 committed by GitHub
commit 7a78aed5a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 2095 additions and 120 deletions

View File

@ -0,0 +1,71 @@
name: 'Setup DJGPP toolchain'
description: 'Download DJGPP and setup CMake toolchain'
runs:
using: 'composite'
steps:
- name: 'Calculate variables'
id: calc
shell: sh
run: |
version="12.2.0"
case "${{ runner.os }}-${{ runner.arch }}" in
"Linux-X86")
archive="djgpp-linux32-gcc1220.tar.bz2"
;;
"Linux-X64")
archive="djgpp-linux64-gcc1220.tar.bz2"
;;
"macOS-X86" | "macOS-X64" | "macOS-ARM64")
archive="djgpp-osx-gcc1220.tar.bz2"
;;
"Windows-X86" | "Windows-X64")
archive="djgpp-mingw-gcc1220.zip"
;;
*)
echo "Unsupported ${{ runner.os }}-${{ runner.arch }}"
exit 1;
;;
esac
echo "url=https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/${archive}" >> ${GITHUB_OUTPUT}
echo "archive=${archive}" >> ${GITHUB_OUTPUT}
echo "version=${version}" >> ${GITHUB_OUTPUT}
echo "cache-key=${archive}-${{ inputs.version }}-${{ runner.os }}-${{ runner.arch }}" >> ${GITHUB_OUTPUT}
- name: 'Restore cached ${{ steps.calc.outputs.archive }}'
id: cache-restore
uses: actions/cache/restore@v5
with:
path: '${{ runner.temp }}/${{ steps.calc.outputs.archive }}'
key: ${{ steps.calc.outputs.cache-key }}
- name: 'Download DJGPP ${{ steps.calc.outputs.version }} for ${{ runner.os }} (${{ runner.arch }})'
if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }}
shell: pwsh
run: |
Invoke-WebRequest "${{ steps.calc.outputs.url }}" -OutFile "${{ runner.temp }}/${{ steps.calc.outputs.archive }}"
- name: 'Cache ${{ steps.calc.outputs.archive }}'
if: ${{ !steps.cache-restore.outputs.cache-hit || steps.cache-restore.outputs.cache-hit == 'false' }}
uses: actions/cache/save@v5
with:
path: '${{ runner.temp }}/${{ steps.calc.outputs.archive }}'
key: ${{ steps.calc.outputs.cache-key }}
- name: 'Extract DJGP archive'
shell: pwsh
run: |
$archive = "${{ steps.calc.outputs.archive }}";
if ($archive.EndsWith(".bz2")) {
# Remove ".bz2" suffix
$tar_archive = $archive.Substring(0, $archive.Length - 4)
7z "-o${{ runner.temp }}" x "${{ runner.temp }}/${{ steps.calc.outputs.archive }}"
7z "-o${{ runner.temp }}" x "${{ runner.temp }}/$tar_archive"
} else {
7z "-o${{ runner.temp }}" x "${{ runner.temp }}/${{ steps.calc.outputs.archive }}"
}
- name: 'Install Linux dependenciy'
if: ${{ runner.os == 'Linux' }}
shell: sh
run: |
sudo apt-get install -y libfl-dev
- name: 'Set output variables'
id: final
shell: pwsh
run: |
echo "${{ runner.temp }}/djgpp/bin" >> $env:GITHUB_PATH

View File

@ -14,7 +14,7 @@ jobs:
name: 'clang-format'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run clang-format
run: |
find CONFIG LEGO1 ISLE miniwin -iname '*.h' -o -iname '*.cpp' | xargs \
@ -48,10 +48,11 @@ jobs:
- { name: 'iOS', os: 'macos-15', generator: 'Xcode', dx5: false, config: false, brew: true, werror: true, clang-tidy: false, cmake-args: '-DCMAKE_SYSTEM_NAME=iOS', ios: true }
- { name: 'Emscripten', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' }
- { name: 'Nintendo 3DS', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, n3ds: true, werror: true, clang-tidy: false, container: 'devkitpro/devkitarm:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake' }
- { name: 'Nintendo Switch', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, nx: true, werror: true, clang-tidy: false, container: 'devkitpro/devkita64:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake' }
- { name: 'Nintendo Switch', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, nx: true, werror: true, clang-tidy: false, container: 'devkitpro/devkita64:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake' }
- { name: 'Xbox One', os: 'windows-latest', generator: 'Visual Studio 17 2022', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64', cmake-args: '-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0', xbox-one: true}
- { name: 'Android', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, android: true, werror: true, clang-tidy: false,}
- { name: 'Vita', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, vita: true, werror: true, clang-tidy: false, cmake-args: '--toolchain /usr/local/vitasdk/share/vita.toolchain.cmake'}
- { name: 'DOS', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, dos: true, werror: true, clang-tidy: false, cmake-args: '--toolchain $GITHUB_WORKSPACE/CMake/i586-pc-msdosdjgpp.cmake'}
steps:
- name: Setup vcvars
if: ${{ !!matrix.msvc }}
@ -134,7 +135,7 @@ jobs:
echo "$VITASDK/bin" >> $GITHUB_PATH
./install-all.sh
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Checkout LFS
if: ${{ matrix.build-assets }}
@ -143,11 +144,15 @@ jobs:
- name: Setup Java (Android)
if: ${{ matrix.android }}
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: 'Set up DJGPP toolchain'
uses: ./.github/actions/setup-djgpp-toolchain
if: ${{ matrix.dos }}
- name: Get CMake (Android)
if: ${{ matrix.android }}
uses: lukka/get-cmake@latest
@ -271,7 +276,7 @@ jobs:
run: (cd build/assets && zip -r ../dist/isle-assets.zip .)
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: '${{ matrix.name }}'
path: |
@ -287,7 +292,7 @@ jobs:
name: 'FreeBSD'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build on FreeBSD
uses: vmactions/freebsd-vm@v1
@ -307,7 +312,7 @@ jobs:
cd build && cpack .
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: 'FreeBSD'
path: build/dist/isle-*
@ -331,7 +336,7 @@ jobs:
options: --privileged
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
@ -345,7 +350,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v1
@ -389,14 +394,14 @@ jobs:
- freebsd
steps:
- name: Download All Artifacts
uses: actions/download-artifact@main
uses: actions/download-artifact@v8
with:
pattern: "*"
path: Release
merge-multiple: true
- name: Checkout uploadtool
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
repository: 'probonopd/uploadtool'
path: 'uploadtool'

View File

@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
@ -48,7 +48,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
uses: actions/attest-build-provenance@v4
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}

View File

@ -5,8 +5,8 @@ if(DOWNLOAD_DEPENDENCIES)
include(FetchContent)
FetchContent_Declare(
miniaudio
URL https://github.com/mackron/miniaudio/archive/refs/tags/0.11.24.tar.gz
URL_MD5 19e8eb21223c56a4a2d167d04decddc9
URL https://github.com/mackron/miniaudio/archive/refs/tags/0.11.25.tar.gz
URL_HASH MD5=6fae6da8f30afb3ddcba26fcaa64f540
)
block()
set(BUILD_SHARED_LIBS OFF)
@ -34,6 +34,12 @@ target_compile_definitions(miniaudio PUBLIC
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)
include(FetchContent)
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;
}

2
3rdparty/miniaudio vendored

@ -1 +1 @@
Subproject commit 13d161bc8d856ad61ae46b798bbeffc0f49808e8
Subproject commit 9634bedb5b5a2ca38c1ee7108a9358a4e233f14d

View File

@ -0,0 +1,17 @@
# DJGPP platform overrides for DOS
#
# CMake's built-in Platform/DOS.cmake assumes OpenWatcom naming conventions
# (no prefix, .lib suffix, CMAKE_LINK_LIBRARY_SUFFIX=".lib"). DJGPP uses
# standard Unix/GCC conventions for its system libraries (lib prefix, .a
# suffix e.g. libm.a).
#
# This file is loaded via CMAKE_USER_MAKE_RULES_OVERRIDE in the toolchain
# file, which runs *after* the platform module has set its defaults, giving
# us the final say on these variables.
set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
set(CMAKE_STATIC_LIBRARY_SUFFIX ".a")
set(CMAKE_LINK_LIBRARY_SUFFIX "")
set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib")
set(CMAKE_EXECUTABLE_SUFFIX ".exe")

View File

@ -0,0 +1,82 @@
set(CMAKE_SYSTEM_NAME DOS)
set(DJGPP TRUE)
# CMake's Platform/DOS.cmake assumes OpenWatcom naming conventions (no prefix,
# .lib suffix). DJGPP uses standard Unix/GCC conventions for its system
# libraries (lib prefix, .a suffix e.g. libm.a), so we override the platform
# defaults via CMAKE_USER_MAKE_RULES_OVERRIDE, which runs *after* the platform
# module has set its defaults, giving us the final say on these variables.
# The path must be cached because CMake re-parses the toolchain file during
# try_compile, where CMAKE_CURRENT_LIST_DIR may point elsewhere.
set(DJGPP_PLATFORM_OVERRIDES "${CMAKE_CURRENT_LIST_DIR}/djgpp-platform-overrides.cmake" CACHE FILEPATH "" FORCE)
set(CMAKE_USER_MAKE_RULES_OVERRIDE "${DJGPP_PLATFORM_OVERRIDES}")
set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
set(CMAKE_STATIC_LIBRARY_SUFFIX ".a")
set(CMAKE_SHARED_LIBRARY_PREFIX "")
set(CMAKE_SHARED_LIBRARY_SUFFIX ".dll")
set(CMAKE_IMPORT_LIBRARY_PREFIX "lib")
set(CMAKE_IMPORT_LIBRARY_SUFFIX ".a")
set(CMAKE_EXECUTABLE_SUFFIX ".exe")
set(CMAKE_LINK_LIBRARY_SUFFIX "")
set(CMAKE_DL_LIBS "")
set(CMAKE_FIND_LIBRARY_PREFIXES "lib")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
#
# CMake toolchain file for DJGPP. Usage:
#
# 1. Download and extract DGJPP
# 2. Add directory containing i586-pc-msdosdjgpp-gcc to PATH environment variable
# 3. When configuring your CMake project, specify the toolchain file like this:
#
# cmake -DCMAKE_TOOLCHAIN_FILE=path/to/i586-pc-msdosdjgpp.cmake ...
#
# specify the cross compiler
find_program(CMAKE_C_COMPILER NAMES "i586-pc-msdosdjgpp-gcc" "i386-pc-msdosdjgpp-gcc" REQUIRED)
find_program(CMAKE_CXX_COMPILER NAMES "i586-pc-msdosdjgpp-g++" "i386-pc-msdosdjgpp-g++" REQUIRED)
execute_process(COMMAND "${CMAKE_C_COMPILER}" -print-search-dirs
RESULT_VARIABLE CC_SEARCH_DIRS_RESULT
OUTPUT_VARIABLE CC_SEARCH_DIRS_OUTPUT)
if(CC_SEARCH_DIRS_RESULT)
message(FATAL_ERROR "Could not determine search dirs")
endif()
string(REGEX MATCH ".*libraries: (.*).*" CC_SD_LIBS "${CC_SEARCH_DIRS_OUTPUT}")
string(STRIP "${CMAKE_MATCH_1}" CC_SEARCH_DIRS)
string(REPLACE ":" ";" CC_SEARCH_DIRS "${CC_SEARCH_DIRS}")
foreach(CC_SEARCH_DIR ${CC_SEARCH_DIRS})
if(CC_SEARCH_DIR MATCHES "=.*")
string(REGEX MATCH "=(.*)" CC_LIB "${CC_SEARCH_DIR}")
set(CC_SEARCH_DIR "${CMAKE_MATCH_1}")
endif()
if(IS_DIRECTORY "${CC_SEARCH_DIR}")
if(IS_DIRECTORY "${CC_SEARCH_DIR}/../include" OR IS_DIRECTORY "${CC_SEARCH_DIR}/../lib" OR IS_DIRECTORY "${CC_SEARCH_DIR}/../bin")
list(APPEND CC_ROOTS "${CC_SEARCH_DIR}/..")
else()
list(APPEND CC_ROOTS "${CC_SEARCH_DIR}")
endif()
endif()
endforeach()
list(APPEND CMAKE_FIND_ROOT_PATH ${CC_ROOTS})
# search for programs in the host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries, headers and packages in the target directories
if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_LIBRARY})
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
endif()
if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_INCLUDE})
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()
if(NOT DEFINED CACHE{CMAKE_FIND_ROOT_PATH_MODE_PACKAGE})
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
endif()

View File

@ -86,15 +86,24 @@ option(ISLE_WERROR "Treat warnings as errors" OFF)
cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF)
cmake_dependent_option(ISLE_MINIWIN "Use miniwin" ON "NOT ISLE_USE_DX5" OFF)
cmake_dependent_option(ISLE_EXTENSIONS "Use extensions" ON "NOT ISLE_USE_DX5;NOT WINDOWS_STORE" OFF)
cmake_dependent_option(ISLE_USE_LWS "Use libwebsockets for native multiplayer" ON "ISLE_EXTENSIONS;NOT EMSCRIPTEN;NOT NINTENDO_3DS;NOT NINTENDO_SWITCH;NOT VITA" OFF)
cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT NINTENDO_SWITCH;NOT WINDOWS_STORE;NOT VITA" OFF)
cmake_dependent_option(ISLE_USE_LWS "Use libwebsockets for native multiplayer" ON "ISLE_EXTENSIONS;NOT DOS;NOT EMSCRIPTEN;NOT NINTENDO_3DS;NOT NINTENDO_SWITCH;NOT VITA" OFF)
cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT DOS;NOT NINTENDO_3DS;NOT NINTENDO_SWITCH;NOT WINDOWS_STORE;NOT VITA" OFF)
cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF)
cmake_dependent_option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON "NOT VITA" OFF)
cmake_dependent_option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON "NOT DOS;NOT VITA" OFF)
option(ENABLE_CLANG_TIDY "Enable clang-tidy")
option(DOWNLOAD_DEPENDENCIES "Download dependencies" ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" CACHE PATH "Directory where to put executables and dll")
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 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 "Config app: ${ISLE_BUILD_CONFIG}")

View File

@ -155,11 +155,19 @@ IsleApp::IsleApp()
m_using8bit = FALSE;
m_using16bit = TRUE;
m_hasLightSupport = FALSE;
#ifdef __DJGPP__
m_drawCursor = TRUE;
#else
m_drawCursor = FALSE;
#endif
m_use3dSound = TRUE;
m_useMusic = TRUE;
m_wideViewAngle = TRUE;
#ifdef __DJGPP__
m_islandQuality = 1;
#else
m_islandQuality = 2;
#endif
m_islandTexture = 1;
m_gameStarted = FALSE;
m_frameDelta = 10;
@ -191,14 +199,22 @@ IsleApp::IsleApp()
m_mediaPath = NULL;
m_iniPath = NULL;
m_maxLod = RealtimeView::GetUserMaxLOD();
#ifdef __DJGPP__
m_maxLod = 1.0f;
#endif
m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20;
m_transitionType = MxTransitionManager::e_mosaic;
m_cursorSensitivity = 4;
m_touchScheme = LegoInputManager::e_gamepad;
m_haptic = TRUE;
m_wasd = FALSE;
#ifdef __DJGPP__
m_xRes = 320;
m_yRes = 200;
#else
m_xRes = 640;
m_yRes = 480;
#endif
m_exclusiveXRes = m_xRes;
m_exclusiveYRes = m_yRes;
m_exclusiveFrameRate = 60.00f;
@ -243,6 +259,10 @@ void IsleApp::Close()
TransitionManager()->SetWaitIndicator(NULL);
Lego()->Resume();
if (BackgroundAudioManager()) {
BackgroundAudioManager()->Stop();
}
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_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];
SDL_snprintf(
buffer,
@ -717,11 +745,17 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
g_lastMouseX = event->motion.x;
g_lastMouseY = event->motion.y;
#ifdef __DJGPP__
if (VideoManager()) {
VideoManager()->MoveCursor(Min((MxS32) g_lastMouseX, 639), Min((MxS32) g_lastMouseY, 479));
}
#else
SDL_ShowCursor();
g_isle->SetDrawCursor(FALSE);
if (VideoManager()) {
VideoManager()->SetCursorBitmap(NULL);
}
#endif
break;
case SDL_EVENT_FINGER_MOTION: {
g_mousemoved = TRUE;
@ -924,6 +958,9 @@ MxResult IsleApp::SetupWindow()
return FAILURE;
}
g_targetWidth = m_xRes;
g_targetHeight = m_yRes;
SetupVideoFlags(
m_fullScreen,
m_flipSurfaces,
@ -954,7 +991,7 @@ MxResult IsleApp::SetupWindow()
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen);
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_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
@ -969,6 +1006,17 @@ MxResult IsleApp::SetupWindow()
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) {
SDL_DisplayMode closestMode;
SDL_DisplayID displayID = SDL_GetDisplayForWindow(window);
@ -983,6 +1031,7 @@ MxResult IsleApp::SetupWindow()
SDL_SetWindowFullscreenMode(window, &closestMode);
}
}
#endif
#ifdef MINIWIN
m_windowHandle = reinterpret_cast<HWND>(window);
@ -1251,7 +1300,7 @@ bool IsleApp::LoadConfig()
m_videoParam.GetRect() = MxRect32(0, 0, (m_xRes - 1), (m_yRes - 1));
}
m_frameRate = (1000.0f / iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta));
m_frameDelta = static_cast<int>(std::round(iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta)));
m_frameDelta = static_cast<int>(iniparser_getdouble(dict, "isle:Frame Delta", m_frameDelta));
m_videoParam.SetMSAASamples((m_msaaSamples = iniparser_getint(dict, "isle:MSAA", m_msaaSamples)));
m_videoParam.SetAnisotropic((m_anisotropic = iniparser_getdouble(dict, "isle:Anisotropic", m_anisotropic)));
m_activeInBackground = iniparser_getboolean(dict, "isle:Active in Background", m_activeInBackground);

View File

@ -88,9 +88,6 @@ class LegoVideoManager : public MxVideoManager {
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
Lego3DManager* m_3dManager; // 0x68
LegoROI* m_viewROI; // 0x6c

View File

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

View File

@ -465,7 +465,45 @@ void LegoVideoManager::DrawFPS()
if (m_unk0x528->Lock(NULL, &surfaceDesc, DDLOCK_WAIT, NULL) == DD_OK) {
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 font[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 = font[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_unk0x550 = 1.f;
@ -789,66 +827,6 @@ MxResult LegoVideoManager::ConfigureD3DRM()
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)
{
if (p_cursorBitmap == NULL) {

View File

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

View File

@ -305,6 +305,7 @@ void MxDisplaySurface::Destroy()
// FUNCTION: BETA10 0x1013fe15
void MxDisplaySurface::SetPalette(MxPalette* p_palette)
{
#ifndef MINIWIN
if ((m_surfaceDesc.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8) == DDPF_PALETTEINDEXED8) {
m_ddSurface1->SetPalette(p_palette->CreateNativePalette());
m_ddSurface2->SetPalette(p_palette->CreateNativePalette());
@ -326,8 +327,10 @@ void MxDisplaySurface::SetPalette(MxPalette* p_palette)
DeleteObject(hpal);
}
}
#else
m_ddSurface1->SetPalette(p_palette->CreateNativePalette());
m_ddSurface2->SetPalette(p_palette->CreateNativePalette());
#ifndef MINIWIN
MxS32 bitCount = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount;
if (bitCount == 8) {
return;
@ -449,17 +452,6 @@ void MxDisplaySurface::VTable0x28(
}
#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;
memset(&tempDesc, 0, sizeof(tempDesc));
tempDesc.dwSize = sizeof(tempDesc);
@ -511,10 +503,10 @@ void MxDisplaySurface::VTable0x28(
if (m_videoParam.Flags().GetDoubleScaling()) {
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 {
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();
@ -1083,10 +1075,6 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::FUN_100bc8b0(MxS32 p_width, MxS32 p_height
return NULL;
}
if (surfaceDesc.ddpfPixelFormat.dwRGBBitCount == 8) {
return NULL;
}
surfaceDesc.dwWidth = p_width;
surfaceDesc.dwHeight = p_height;
surfaceDesc.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;

View File

@ -238,7 +238,9 @@ inline void ViewManager::ManageVisibilityAndDetailRecursively(ViewROI* p_from, i
const CompoundObject* comp = p_from->GetComp();
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());
if (RealtimeView::GetUserMaxLOD() <= 5.0f && projectedSize < seconds_allowed * g_viewDistance) {

View File

@ -69,6 +69,12 @@ if(NOT (VITA OR WINDOWS_STORE))
endif()
endif()
if(DOS)
list(REMOVE_ITEM GRAPHICS_BACKENDS USE_SDL_GPU USE_OPENGL1 USE_OPENGLES2) #USE_SDL_GPU
endif()
list(APPEND GRAPHICS_BACKENDS USE_PALETTE_SW_RENDER)
if(NINTENDO_SWITCH)
# Remove USE_OPENGL1 as incompatible.
# Remove everything else as not needed.
@ -139,6 +145,12 @@ if(USE_SOFTWARE_RENDER IN_LIST GRAPHICS_BACKENDS)
)
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_include_directories(miniwin

File diff suppressed because it is too large Load Diff

View File

@ -64,14 +64,14 @@ void Direct3DRMSoftwareRenderer::ClearZBuffer()
const float inf = std::numeric_limits<float>::infinity();
size_t i = 0;
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
#if (defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)) && defined(__SSE2__)
if (SDL_HasSSE2()) {
__m128 inf4 = _mm_set1_ps(inf);
for (; i + 4 <= size; i += 4) {
_mm_storeu_ps(&m_zBuffer[i], inf4);
}
}
#if defined(__i386__) || defined(_M_IX86)
#if (defined(__i386__) || defined(_M_IX86)) && defined(__MMX__)
else if (SDL_HasMMX()) {
const __m64 mm_inf = _mm_set_pi32(0x7F800000, 0x7F800000);
for (; i + 2 <= size; i += 2) {
@ -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)
{
SDL_Rect srcRect = {

View File

@ -20,6 +20,9 @@
#ifdef USE_SOFTWARE_RENDER
#include "d3drmrenderer_software.h"
#endif
#ifdef USE_PALETTE_SW_RENDER
#include "d3drmrenderer_palettesw.h"
#endif
#ifdef USE_GXM
#include "d3drmrenderer_gxm.h"
#endif
@ -74,6 +77,11 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer(
if (SDL_memcmp(guid, &GXM_GUID, sizeof(GUID)) == 0) {
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
return nullptr;
}
@ -101,6 +109,9 @@ void Direct3DRMRenderer_EnumDevices(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICE
#ifdef USE_SOFTWARE_RENDER
Direct3DRMSoftware_EnumDevice(cb, ctx);
#endif
#ifdef USE_PALETTE_SW_RENDER
Direct3DRMPaletteSW_EnumDevice(cb, ctx);
#endif
#ifdef USE_GXM
GXMRenderer_EnumDevice(cb, ctx);
#endif

View File

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

View File

@ -53,13 +53,20 @@ HRESULT DirectDrawSurfaceImpl::Blt(
)
{
if ((dwFlags & DDBLT_COLORFILL) == DDBLT_COLORFILL) {
Uint8 a = (lpDDBltFx->dwFillColor >> 24) & 0xFF;
Uint8 r = (lpDDBltFx->dwFillColor >> 16) & 0xFF;
Uint8 g = (lpDDBltFx->dwFillColor >> 8) & 0xFF;
Uint8 b = lpDDBltFx->dwFillColor & 0xFF;
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 r = (lpDDBltFx->dwFillColor >> 16) & 0xFF;
Uint8 g = (lpDDBltFx->dwFillColor >> 8) & 0xFF;
Uint8 b = lpDDBltFx->dwFillColor & 0xFF;
const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_surface->format);
Uint32 color = SDL_MapRGBA(details, nullptr, r, g, b, a);
const SDL_PixelFormatDetails* details = SDL_GetPixelFormatDetails(m_surface->format);
color = SDL_MapRGBA(details, nullptr, r, g, b, a);
}
if (lpDestRect) {
SDL_Rect dstRect = ConvertRect(lpDestRect);
SDL_FillSurfaceRect(m_surface, &dstRect, color);

View File

@ -9,7 +9,11 @@
FrameBufferImpl::FrameBufferImpl(DWORD virtualWidth, DWORD 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);
#endif
}
FrameBufferImpl::~FrameBufferImpl()
@ -49,7 +53,7 @@ HRESULT FrameBufferImpl::Blt(
return DDERR_GENERIC;
}
if (dynamic_cast<FrameBufferImpl*>(lpDDSrcSurface) == this) {
if (dynamic_cast<FrameBufferImpl*>(lpDDSrcSurface)) {
return Flip(nullptr, DDFLIP_WAIT);
}
@ -103,7 +107,11 @@ HRESULT FrameBufferImpl::BltFast(
int height = lpSrcRect ? (lpSrcRect->bottom - lpSrcRect->top) : surface->m_surface->h;
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)
@ -210,8 +218,11 @@ HRESULT FrameBufferImpl::SetColorKey(DDColorKeyFlags dwFlags, LPDDCOLORKEY lpDDC
HRESULT FrameBufferImpl::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette)
{
// If the transfer buffer is not INDEX8 yet, recreate it.
// SetPalette being called proves the game wants 8-bit paletted mode.
if (m_transferBuffer->m_surface->format != SDL_PIXELFORMAT_INDEX8) {
MINIWIN_NOT_IMPLEMENTED();
m_transferBuffer->Release();
m_transferBuffer = new DirectDrawSurfaceImpl(m_virtualWidth, m_virtualHeight, SDL_PIXELFORMAT_INDEX8);
}
lpDDPalette->AddRef();
@ -222,6 +233,11 @@ HRESULT FrameBufferImpl::SetPalette(LPDIRECTDRAWPALETTE lpDDPalette)
m_palette = lpDDPalette;
SDL_SetSurfacePalette(m_transferBuffer->m_surface, ((DirectDrawPaletteImpl*) m_palette)->m_palette);
if (DDRenderer) {
DDRenderer->SetPalette(((DirectDrawPaletteImpl*) m_palette)->m_palette);
}
return DD_OK;
}

View File

@ -55,6 +55,7 @@ class Direct3DRMRenderer : public IDirect3DDevice2 {
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 SetDither(bool dither) = 0;
virtual void SetPalette(SDL_Palette* palette) {}
protected:
int m_width, m_height;

View File

@ -0,0 +1,115 @@
#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;
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 Download(SDL_Surface* target) override;
void SetDither(bool dither) override;
void SetPalette(SDL_Palette* palette) override;
private:
void ClearZBuffer();

View File

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

View File

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