Merge branch 'master' of github.com:isledecomp/isle-portable into psp

This commit is contained in:
Anders Jenbo 2025-07-01 21:50:46 +02:00
commit 87072cc230
70 changed files with 2816 additions and 999 deletions

View File

@ -14,3 +14,8 @@ trim_trailing_whitespace = true
[{CMakeLists.txt,*.cmake}]
indent_size = 2
insert_final_newline = true
[*.{json,xml.in,desktop.in}]
indent_size = 2
insert_final_newline = true

4
.gitattributes vendored
View File

@ -5,3 +5,7 @@
*.html text eol=lf diff=html
*.mdp binary
*.mak text eol=crlf
**/*.ico binary
**/*.png binary
**/*.svg text eol=lf
**/*.desktop text eol=lf

View File

@ -25,6 +25,7 @@ jobs:
build:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
container: ${{ matrix.container || '' }}
defaults:
run:
shell: ${{ matrix.shell || 'sh' }}
@ -33,14 +34,15 @@ jobs:
fail-fast: false
matrix:
include:
- { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, build-type: 'Debug', linux: true, werror: true, clang-tidy: true }
- { name: 'MSVC (x86)', os: 'windows-latest', dx5: true, config: false, build-type: 'Debug', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' }
- { name: 'MSVC (x64)', os: 'windows-latest', dx5: false, config: false, build-type: 'Debug', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' }
- { name: 'MSVC (arm64)', os: 'windows-latest', dx5: false, config: false, build-type: 'Debug', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' }
- { name: 'msys2 mingw32', os: 'windows-latest', dx5: false, config: false, build-type: 'Debug', mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' }
- { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, build-type: 'Debug', mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' }
- { name: 'macOS', os: 'macos-latest', dx5: false, config: true, build-type: 'Debug', brew: true, werror: true, clang-tidy: false }
- { name: 'Emscripten', os: 'ubuntu-latest', dx5: false, config: false, build-type: 'Debug', emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' }
- { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, linux: true, werror: true, clang-tidy: true }
- { name: 'MSVC (x86)', os: 'windows-latest', dx5: true, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' }
- { name: 'MSVC (x64)', os: 'windows-latest', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' }
- { name: 'MSVC (arm64)', os: 'windows-latest', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_arm64' }
- { name: 'msys2 mingw32', os: 'windows-latest', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' }
- { name: 'msys2 mingw64', os: 'windows-latest', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' }
- { name: 'macOS', os: 'macos-latest', dx5: false, config: true, brew: true, werror: true, clang-tidy: false }
- { name: 'Emscripten', os: 'ubuntu-latest', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' }
- { name: 'Nintendo 3DS', os: 'ubuntu-latest', 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' }
steps:
- name: Setup vcvars
if: ${{ !!matrix.msvc }}
@ -66,8 +68,8 @@ jobs:
sudo apt-get update
sudo apt-get install -y \
libx11-dev libxext-dev libxrandr-dev libxrender-dev libxfixes-dev libxi-dev libxinerama-dev \
libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev libglew-dev qt6-base-dev \
libasound2-dev
libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \
libasound2-dev qt6-xdgdesktopportal-platformtheme
- name: Install macOS dependencies (brew)
if: ${{ matrix.brew }}
@ -89,26 +91,90 @@ jobs:
- name: Configure (CMake)
run: |
${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
${{ matrix.cmake-args || '' }} \
-DCMAKE_BUILD_TYPE=Release \
-DISLE_USE_DX5=${{ !!matrix.dx5 }} \
-DISLE_BUILD_CONFIG=${{ matrix.config }} \
-DISLE_BUILD_CONFIG=${{ !!matrix.config }} \
-DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \
-DISLE_WERROR=${{ !!matrix.werror }} \
-DISLE_DEBUG=OFF \
-Werror=dev
- name: Build (CMake)
run: cmake --build build --verbose
- name: Package (CPack)
if: ${{ !matrix.n3ds }}
run: |
cd build
cpack .
- name: Install linuxdeploy
if: ${{ matrix.linux }}
id: install-linuxdeploy
uses: miurahr/install-linuxdeploy-action@v1.8.0
with:
plugins: qt appimage
- name: Package (AppImage)
if: ${{ matrix.linux }}
run: |
cd build && \
export LD_LIBRARY_PATH=".:$LD_LIBRARY_PATH" && \
NO_STRIP=1 ${{ steps.install-linuxdeploy.outputs.linuxdeploy }} \
-p qt \
-e isle \
-e isle-config \
-d packaging/linux/org.legoisland.Isle.desktop \
-i icons/org.legoisland.Isle.svg \
--custom-apprun=../packaging/linux/appimage/AppRun \
--appdir packaging/linux/appimage/AppDir \
--output appimage && \
mv *.AppImage dist/
- name: Package (3DS)
if: ${{ matrix.n3ds }}
run: |
cd build
mkdir dist
mv *.3dsx dist/
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: '${{ matrix.name }} ${{ matrix.build-type }}'
path: build/dist/isle-*
name: '${{ matrix.name }}'
path: |
build/dist/isle-*
build/dist/*.AppImage
build/dist/*.3dsx
flatpak:
name: "Flatpak (${{ matrix.arch }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
os: ubuntu-latest
- arch: aarch64
os: ubuntu-22.04-arm
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.8
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: org.legoisland.Isle.${{ matrix.arch }}.flatpak
manifest-path: packaging/linux/flatpak/org.legoisland.Isle.json
arch: ${{ matrix.arch }}
ncc:
name: 'C++'
@ -148,3 +214,30 @@ jobs:
LEGO1/omni/src/video/flic.cpp \
$action_headers \
--path LEGO1/omni LEGO1/lego/legoomni
release:
name: 'Release'
if: ${{ github.event_name == 'push' && github.ref_name == 'master' }}
runs-on: ubuntu-latest
needs:
- build
- flatpak
steps:
- name: Download All Artifacts
uses: actions/download-artifact@main
with:
pattern: "*"
path: Release
merge-multiple: true
- name: Checkout uploadtool
uses: actions/checkout@v4
with:
repository: 'probonopd/uploadtool'
path: 'uploadtool'
- name: Upload Continuous Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./uploadtool/upload.sh Release/*

View File

@ -1,116 +0,0 @@
name: Release
on:
push:
branches:
- master
jobs:
build:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
defaults:
run:
shell: ${{ matrix.shell || 'sh' }}
strategy:
fail-fast: false
matrix:
include:
- { name: 'Linux', os: 'ubuntu-latest', dx5: false, config: true, build-type: 'Release', linux: true, werror: true, clang-tidy: false }
- { name: 'Windows', os: 'windows-latest', dx5: false, config: false, build-type: 'Release', msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' }
- { name: 'macOS', os: 'macos-latest', dx5: false, config: true, build-type: 'Release', brew: true, werror: true, clang-tidy: false }
steps:
- name: Setup vcvars
if: ${{ !!matrix.msvc }}
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.vc-arch }}
- name: Set up MSYS2
if: ${{ !!matrix.msystem }}
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.msystem }}
install: >-
${{ matrix.msys-env }}-cc
${{ matrix.msys-env }}-cmake
${{ matrix.msys-env }}-ninja
${{ matrix.msys-env }}-clang-tools-extra
${{ (matrix.config && format('{0}-qt6-base', matrix.msys-env)) || '' }}
- name: Install Linux dependencies (apt-get)
if: ${{ matrix.linux }}
run: |
sudo apt-get update
sudo apt-get install -y \
libx11-dev libxext-dev libxrandr-dev libxrender-dev libxfixes-dev libxi-dev libxinerama-dev \
libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev libglew-dev qt6-base-dev \
libasound2-dev
- name: Install macOS dependencies (brew)
if: ${{ matrix.brew }}
run: |
brew update
brew install cmake ninja llvm qt6
echo "LLVM_ROOT=$(brew --prefix llvm)/bin" >> $GITHUB_ENV
- name: Setup Emscripten
uses: mymindstorm/setup-emsdk@master
if: ${{ matrix.emsdk }}
- name: Setup ninja
if: ${{ matrix.msvc }}
uses: ashutoshvarma/setup-ninja@master
- uses: actions/checkout@v4
- name: Configure (CMake)
run: |
${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DISLE_USE_DX5=${{ !!matrix.dx5 }} \
-DISLE_BUILD_CONFIG=${{ matrix.config }} \
-DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \
-DISLE_WERROR=${{ !!matrix.werror }} \
-DISLE_DEBUG=OFF \
-Werror=dev
- name: Build (CMake)
run: cmake --build build --verbose
- name: Package (CPack)
run: |
cd build
cpack .
- name: Upload Artifact
uses: actions/upload-artifact@main
with:
name: Release-${{ matrix.name }}
path: |
build/dist/isle-*
release:
name: 'Release'
runs-on: ubuntu-latest
needs: build
steps:
- name: Download All Artifacts
uses: actions/download-artifact@main
with:
pattern: Release-*
path: Release
merge-multiple: true
- name: Checkout uploadtool
uses: actions/checkout@v4
with:
repository: 'probonopd/uploadtool'
path: 'uploadtool'
- name: Upload Continuous Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./uploadtool/upload.sh Release/*

6
.gitignore vendored
View File

@ -30,3 +30,9 @@ LEGO1.DLL
# Kate - Text
/.cache
# Flatpak build cache
**/.flatpak-builder/
# Flatpak build dir
**/flatpak-build/

View File

@ -13,6 +13,9 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
include(CheckCXXSourceCompiles)
include(CMakeDependentOption)
include(CMakePushCheckState)
include(cmake/detectcpu.cmake)
DetectTargetCPUArchitectures(ISLE_CPUS)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -35,7 +38,7 @@ option(ISLE_WERROR "Treat warnings as errors" OFF)
option(ISLE_DEBUG "Enable imgui debug" ON)
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_BUILD_CONFIG "Build CONFIG.EXE application" ON "(MSVC OR ISLE_MINIWIN) AND (NOT PSP)" OFF)
cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "(MSVC OR ISLE_MINIWIN) AND (NOT PSP) AND (NOT NINTENDO_3DS)" 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 PSP" OFF)
option(ENABLE_CLANG_TIDY "Enable clang-tidy")
@ -532,6 +535,12 @@ if (ISLE_BUILD_APP)
target_compile_definitions(isle PRIVATE "ISLE_EMSCRIPTEN_HOST=\"${ISLE_EMSCRIPTEN_HOST}\"")
set_property(TARGET isle PROPERTY SUFFIX ".html")
endif()
if(NINTENDO_3DS)
target_sources(isle PRIVATE
ISLE/3ds/apthooks.cpp
ISLE/3ds/config.cpp
)
endif()
endif()
if (ISLE_BUILD_CONFIG)
@ -636,7 +645,9 @@ else()
include(GNUInstallDirs)
endif()
set(ISLE_PACKAGE_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}" CACHE STRING "Platform name of the package")
string(REPLACE ";" "-" ISLE_CPUS_STRING "${ISLE_CPUS}")
string(TOLOWER "${ISLE_CPUS_STRING}" ISLE_CPUS_STRING)
set(ISLE_PACKAGE_NAME "${CMAKE_SYSTEM_NAME}-${ISLE_CPUS_STRING}" CACHE STRING "Platform name of the package")
if(BUILD_SHARED_LIBS)
list(APPEND install_extra_targets lego1)
endif()
@ -655,8 +666,23 @@ if(EMSCRIPTEN)
)
endif()
add_subdirectory(packaging)
set(CPACK_PACKAGE_DIRECTORY "dist")
set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}")
if(NINTENDO_3DS)
ctr_generate_smdh(isle.smdh
NAME "LEGO Island"
TITLE "LEGO Island"
DESCRIPTION "LEGO Island for the Nintendo 3DS"
AUTHOR "isledecomp/isle-portable"
VERSION "${PROJECT_VERSION}"
ICON "ISLE/res/3ds/isle.png"
)
ctr_create_3dsx(isle SMDH isle.smdh)
install(FILES "$<TARGET_FILE_DIR:isle>/isle.3dsx" DESTINATION "${CMAKE_INSTALL_BINDIR}")
endif()
if(MSVC)
set(CPACK_GENERATOR ZIP)
elseif(PSP)
@ -674,4 +700,5 @@ elseif(PSP)
else()
set(CPACK_GENERATOR TGZ)
endif()
include(CPack)

View File

@ -56,9 +56,17 @@ bool CConfigApp::InitInstance()
return false;
}
m_device_enumerator = new LegoDeviceEnumerate;
if (m_device_enumerator->DoEnumerate()) {
SDL_Window* window = SDL_CreateWindow("Test window", 640, 480, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
HWND hWnd;
#ifdef MINIWIN
hWnd = reinterpret_cast<HWND>(window);
#else
hWnd = (HWND) SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
#endif
if (m_device_enumerator->DoEnumerate(hWnd)) {
return FALSE;
}
SDL_DestroyWindow(window);
m_driver = NULL;
m_device = NULL;
m_full_screen = TRUE;

30
ISLE/3ds/apthooks.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "apthooks.h"
#include "legomain.h"
#include "misc.h"
aptHookCookie g_aptCookie;
void N3DS_AptHookCallback(APT_HookType hookType, void* param)
{
switch (hookType) {
case APTHOOK_ONSLEEP:
case APTHOOK_ONSUSPEND:
Lego()->Pause();
break;
case APTHOOK_ONWAKEUP:
case APTHOOK_ONRESTORE:
Lego()->Resume();
break;
case APTHOOK_ONEXIT:
Lego()->CloseMainWindow();
break;
default:
break;
}
}
void N3DS_SetupAptHooks()
{
aptHook(&g_aptCookie, N3DS_AptHookCallback, NULL);
}

9
ISLE/3ds/apthooks.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef N3DS_APTHOOKS_H
#define N3DS_APTHOOKS_H
#include <3ds.h>
void N3DS_AptHookCallback(APT_HookType hookType, void* param);
void N3DS_SetupAptHooks();
#endif // N3DS_APTHOOKS_H

22
ISLE/3ds/config.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "config.h"
#include <SDL3/SDL_log.h>
#include <iniparser.h>
void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary)
{
SDL_Log("Overriding default config for 3DS");
// We are currently not bundling the assets into romfs.
// User must place assets in sdmc:/3ds/isle where
// sdmc:/3ds/isle/LEGO/SCRIPTS/CREDITS.si exists, for example.
iniparser_set(p_dictionary, "isle:diskpath", "sdmc:/3ds/isle/LEGO/disk");
iniparser_set(p_dictionary, "isle:cdpath", "sdmc:/3ds/isle");
// TODO: Save path: can we use libctru FS save data functions? Would be neat, especially for CIA install
// Extra / at the end causes some issues
iniparser_set(p_dictionary, "isle:savepath", "sdmc:/3ds/isle");
// Use e_noAnimation/cut transition
iniparser_set(p_dictionary, "isle:Transition Type", "1");
}

8
ISLE/3ds/config.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef N3DS_CONFIG_H
#define N3DS_CONFIG_H
#include "dictionary.h"
void N3DS_SetupDefaultConfigOverrides(dictionary* p_dictionary);
#endif // N3DS_CONFIG_H

View File

@ -50,6 +50,11 @@
#include "emscripten/messagebox.h"
#endif
#ifdef __3DS__
#include "3ds/apthooks.h"
#include "3ds/config.h"
#endif
DECOMP_SIZE_ASSERT(IsleApp, 0x8c)
// GLOBAL: ISLE 0x410030
@ -96,11 +101,7 @@ IsleApp::IsleApp()
m_cdPath = NULL;
m_deviceId = NULL;
m_savePath = NULL;
#ifdef __EMSCRIPTEN__
m_fullScreen = FALSE;
#else
m_fullScreen = TRUE;
#endif
m_flipSurfaces = FALSE;
m_backBuffersInVram = TRUE;
m_using8bit = FALSE;
@ -140,6 +141,7 @@ IsleApp::IsleApp()
m_iniPath = NULL;
m_maxLod = RealtimeView::GetUserMaxLOD();
m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20;
m_transitionType = MxTransitionManager::e_mosaic;
}
// FUNCTION: ISLE 0x4011a0
@ -316,6 +318,9 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
},
NULL
);
#endif
#ifdef __3DS__
N3DS_SetupAptHooks();
#endif
return SDL_APP_CONTINUE;
}
@ -657,9 +662,10 @@ 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);
#ifdef MINIWIN
#if defined(MINIWIN) && !defined(__3DS__)
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);
#endif
window = SDL_CreateWindowWithProperties(props);
@ -726,6 +732,7 @@ MxResult IsleApp::SetupWindow()
LegoBuildingManager::configureLegoBuildingManager(m_islandQuality);
LegoROI::configureLegoROI(iVar10);
LegoAnimationManager::configureLegoAnimationManager(m_maxAllowedExtras);
MxTransitionManager::configureMxTransitionManager(m_transitionType);
RealtimeView::SetUserMaxLOD(m_maxLod);
if (LegoOmni::GetInstance()) {
if (LegoOmni::GetInstance()->GetInputManager()) {
@ -824,7 +831,11 @@ bool IsleApp::LoadConfig()
SDL_snprintf(buf, sizeof(buf), "%f", m_maxLod);
iniparser_set(dict, "isle:Max LOD", buf);
iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10));
iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10));
#ifdef __3DS__
N3DS_SetupDefaultConfigOverrides(dict);
#endif
iniparser_dump_ini(dict, iniFP);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig);
fclose(iniFP);
@ -853,7 +864,13 @@ bool IsleApp::LoadConfig()
strcpy(m_mediaPath, mediaPath);
m_flipSurfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flipSurfaces);
#ifdef __EMSCRIPTEN__
m_fullScreen = FALSE;
#else
m_fullScreen = iniparser_getboolean(dict, "isle:Full Screen", m_fullScreen);
#endif
m_wideViewAngle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wideViewAngle);
m_use3dSound = iniparser_getboolean(dict, "isle:3DSound", m_use3dSound);
m_useMusic = iniparser_getboolean(dict, "isle:Music", m_useMusic);
@ -880,6 +897,8 @@ bool IsleApp::LoadConfig()
m_islandTexture = iniparser_getint(dict, "isle:Island Texture", m_islandTexture);
m_maxLod = iniparser_getdouble(dict, "isle:Max LOD", m_maxLod);
m_maxAllowedExtras = iniparser_getint(dict, "isle:Max Allowed Extras", m_maxAllowedExtras);
m_transitionType =
(MxTransitionManager::TransitionType) iniparser_getint(dict, "isle:Transition Type", m_transitionType);
const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL);
if (deviceId != NULL) {

View File

@ -3,6 +3,7 @@
#include "lego1_export.h"
#include "legoutils.h"
#include "mxtransitionmanager.h"
#include "mxtypes.h"
#include "mxvideoparam.h"
@ -91,9 +92,11 @@ class IsleApp {
char* m_iniPath;
MxFloat m_maxLod;
MxU32 m_maxAllowedExtras;
MxTransitionManager::TransitionType m_transitionType;
};
extern IsleApp* g_isle;
extern MxS32 g_closed;
extern IDirect3DRMMiniwinDevice* GetD3DRMMiniwinDevice();

View File

@ -309,7 +309,7 @@ void IsleDebug_Render()
if (ImGui::TreeNode("Sound Manager")) {
LegoSoundManager* soundManager = lego->GetSoundManager();
Sint32 oldVolume = soundManager->GetVolume();
Sint32 volume = oldVolume;
int volume = oldVolume;
ImGui::SliderInt("volume", &volume, 0, 100);
if (volume != oldVolume) {
soundManager->SetVolume(volume);

BIN
ISLE/res/3ds/isle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

View File

@ -123,7 +123,7 @@ class Hospital : public LegoWorld {
MxLong m_copLedAnimTimer; // 0x11c
MxLong m_pizzaLedAnimTimer; // 0x120
MxLong m_time; // 0x124
undefined m_unk0x128; // 0x128
MxBool m_exited; // 0x128
};
#endif // HOSPITAL_H

View File

@ -121,7 +121,7 @@ class LegoAnimPresenter : public MxVideoPresenter {
void SubstituteVariables();
void FUN_1006b900(LegoAnim* p_anim, MxLong p_time, Matrix4* p_matrix);
void FUN_1006b9a0(LegoAnim* p_anim, MxLong p_time, Matrix4* p_matrix);
void FUN_1006c8a0(MxBool p_bool);
void SetDisabled(MxBool p_disabled);
LegoAnim* m_anim; // 0x64
LegoROI** m_roiMap; // 0x68

View File

@ -79,7 +79,7 @@ class LegoBuildingManager : public MxCore {
MxBool SwitchMove(LegoEntity* p_entity);
MxBool SwitchMood(LegoEntity* p_entity);
MxU32 GetAnimationId(LegoEntity* p_entity);
MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_state);
MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood);
MxBool DecrementCounter(LegoEntity* p_entity);
MxBool DecrementCounter(MxS32 p_index);
MxBool DecrementCounter(LegoBuildingInfo* p_data);

View File

@ -88,7 +88,7 @@ class LegoCharacterManager {
MxBool SwitchMove(LegoROI* p_roi);
MxBool SwitchMood(LegoROI* p_roi);
MxU32 GetAnimationId(LegoROI* p_roi);
MxU32 GetSoundId(LegoROI* p_roi, MxBool p_und);
MxU32 GetSoundId(LegoROI* p_roi, MxBool p_basedOnMood);
MxU8 GetMood(LegoROI* p_roi);
LegoROI* CreateAutoROI(const char* p_name, const char* p_lodName, MxBool p_createEntity);
MxResult UpdateBoundingSphereAndBox(LegoROI* p_roi);

View File

@ -28,7 +28,7 @@ class LegoEntity : public MxEntity {
};
enum {
c_altBit1 = 0x01
c_disabled = 0x01
};
LegoEntity() { Init(); }
@ -68,13 +68,13 @@ class LegoEntity : public MxEntity {
// FUNCTION: BETA10 0x10013260
virtual void SetWorldSpeed(MxFloat p_worldSpeed) { m_worldSpeed = p_worldSpeed; } // vtable+0x30
virtual void ClickSound(MxBool p_und); // vtable+0x34
virtual void ClickAnimation(); // vtable+0x38
virtual void SwitchVariant(); // vtable+0x3c
virtual void SwitchSound(); // vtable+0x40
virtual void SwitchMove(); // vtable+0x44
virtual void SwitchColor(LegoROI* p_roi); // vtable+0x48
virtual void SwitchMood(); // vtable+0x4c
virtual void ClickSound(MxBool p_basedOnMood); // vtable+0x34
virtual void ClickAnimation(); // vtable+0x38
virtual void SwitchVariant(); // vtable+0x3c
virtual void SwitchSound(); // vtable+0x40
virtual void SwitchMove(); // vtable+0x44
virtual void SwitchColor(LegoROI* p_roi); // vtable+0x48
virtual void SwitchMood(); // vtable+0x4c
void FUN_10010c30();
void SetType(MxU8 p_type);
@ -83,7 +83,7 @@ class LegoEntity : public MxEntity {
Mx3DPointFloat GetWorldUp();
Mx3DPointFloat GetWorldPosition();
MxBool GetUnknown0x10IsSet(MxU8 p_flag) { return m_unk0x10 & p_flag; }
MxBool IsInteraction(MxU8 p_flag) { return m_interaction & p_flag; }
MxBool GetFlagsIsSet(MxU8 p_flag) { return m_flags & p_flag; }
MxU8 GetFlags() { return m_flags; }
@ -101,14 +101,14 @@ class LegoEntity : public MxEntity {
void SetFlags(MxU8 p_flags) { m_flags = p_flags; }
void SetFlag(MxU8 p_flag) { m_flags |= p_flag; }
void ClearFlag(MxU8 p_flag) { m_flags &= ~p_flag; }
void SetUnknown0x10Flag(MxU8 p_flag) { m_unk0x10 |= p_flag; }
void ClearUnknown0x10Flag(MxU8 p_flag) { m_unk0x10 &= ~p_flag; }
void SetInteractionFlag(MxU8 p_flag) { m_interaction |= p_flag; }
void ClearInteractionFlag(MxU8 p_flag) { m_interaction &= ~p_flag; }
protected:
void Init();
void SetWorld();
MxU8 m_unk0x10; // 0x10
MxU8 m_interaction; // 0x10
MxU8 m_flags; // 0x11
Mx3DPointFloat m_worldLocation; // 0x14
Mx3DPointFloat m_worldDirection; // 0x28

View File

@ -49,7 +49,7 @@ class LEGO1_EXPORT LegoPlantManager : public MxCore {
MxBool SwitchMove(LegoEntity* p_entity);
MxBool SwitchMood(LegoEntity* p_entity);
MxU32 GetAnimationId(LegoEntity* p_entity);
MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_state);
MxU32 GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood);
LegoPlantInfo* GetInfoArray(MxS32& p_length);
LegoEntity* CreatePlant(MxS32 p_index, LegoWorld* p_world, LegoOmni::World p_worldId);
MxBool DecrementCounter(LegoEntity* p_entity);

View File

@ -55,6 +55,8 @@ class MxTransitionManager : public MxCore {
TransitionType GetTransitionType() { return m_mode; }
LEGO1_EXPORT static void configureMxTransitionManager(TransitionType p_transitionManagerConfig);
// SYNTHETIC: LEGO1 0x1004b9e0
// MxTransitionManager::`scalar deleting destructor'

View File

@ -199,10 +199,10 @@ LegoBuildingInfo g_buildingInfoInit[16] = {
MxU32 LegoBuildingManager::g_maxSound = 6;
// GLOBAL: LEGO1 0x100f373c
MxU32 g_unk0x100f373c = 0x3c;
MxU32 g_buildingSoundIdOffset = 0x3c;
// GLOBAL: LEGO1 0x100f3740
MxU32 g_unk0x100f3740 = 0x42;
MxU32 g_buildingSoundIdMoodOffset = 0x42;
// clang-format off
// GLOBAL: LEGO1 0x100f3788
@ -227,6 +227,8 @@ LegoBuildingInfo g_buildingInfo[16];
// GLOBAL: LEGO1 0x100f3748
MxS32 LegoBuildingManager::g_maxMove[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0};
#define HAUS1_INDEX 12
// FUNCTION: LEGO1 0x1002f8b0
void LegoBuildingManager::configureLegoBuildingManager(MxS32 p_buildingManagerConfig)
{
@ -392,6 +394,9 @@ MxResult LegoBuildingManager::Read(LegoStorage* p_storage)
m_nextVariant = 0;
}
// Bugfix: allow Pepper to change variant building after save game load
g_buildingInfo[HAUS1_INDEX].m_variant = g_buildingInfoVariants[m_nextVariant];
result = SUCCESS;
done:
@ -461,7 +466,7 @@ MxBool LegoBuildingManager::SwitchVariant(LegoEntity* p_entity)
roi->SetVisibility(FALSE);
info->m_variant = g_buildingInfoVariants[m_nextVariant];
CreateBuilding(12, CurrentWorld());
CreateBuilding(HAUS1_INDEX, CurrentWorld());
if (info->m_entity != NULL) {
info->m_entity->GetROI()->SetVisibility(TRUE);
@ -548,7 +553,7 @@ MxU32 LegoBuildingManager::GetAnimationId(LegoEntity* p_entity)
// FUNCTION: LEGO1 0x1002ff40
// FUNCTION: BETA10 0x10064398
MxU32 LegoBuildingManager::GetSoundId(LegoEntity* p_entity, MxBool p_state)
MxU32 LegoBuildingManager::GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood)
{
LegoBuildingInfo* info = GetInfo(p_entity);
@ -556,12 +561,12 @@ MxU32 LegoBuildingManager::GetSoundId(LegoEntity* p_entity, MxBool p_state)
return 0;
}
if (p_state) {
return info->m_mood + g_unk0x100f3740;
if (p_basedOnMood) {
return info->m_mood + g_buildingSoundIdMoodOffset;
}
if (info != NULL) {
return info->m_sound + g_unk0x100f373c;
return info->m_sound + g_buildingSoundIdOffset;
}
return 0;

View File

@ -39,10 +39,10 @@ MxU32 g_characterAnimationId = 10;
char* LegoCharacterManager::g_customizeAnimFile = NULL;
// GLOBAL: LEGO1 0x100fc4d8
MxU32 g_soundIdOffset = 50;
MxU32 g_characterSoundIdOffset = 50;
// GLOBAL: LEGO1 0x100fc4dc
MxU32 g_soundIdMoodOffset = 66;
MxU32 g_characterSoundIdMoodOffset = 66;
// GLOBAL: LEGO1 0x100fc4e8
MxU32 g_headTextureCounter = 0;
@ -933,16 +933,16 @@ MxU32 LegoCharacterManager::GetAnimationId(LegoROI* p_roi)
// FUNCTION: LEGO1 0x10085140
// FUNCTION: BETA10 0x10076855
MxU32 LegoCharacterManager::GetSoundId(LegoROI* p_roi, MxBool p_und)
MxU32 LegoCharacterManager::GetSoundId(LegoROI* p_roi, MxBool p_basedOnMood)
{
LegoActorInfo* info = GetActorInfo(p_roi);
if (p_und) {
return info->m_mood + g_soundIdMoodOffset;
if (p_basedOnMood) {
return info->m_mood + g_characterSoundIdMoodOffset;
}
if (info != NULL) {
return info->m_sound + g_soundIdOffset;
return info->m_sound + g_characterSoundIdOffset;
}
else {
return 0;

View File

@ -40,10 +40,10 @@ MxU8 g_counters[] = {1, 2, 2, 3};
MxU32 LegoPlantManager::g_maxSound = 8;
// GLOBAL: LEGO1 0x100f3160
MxU32 g_unk0x100f3160 = 56;
MxU32 g_plantSoundIdOffset = 56;
// GLOBAL: LEGO1 0x100f3164
MxU32 g_unk0x100f3164 = 66;
MxU32 g_plantSoundIdMoodOffset = 66;
// GLOBAL: LEGO1 0x100f3168
MxS32 LegoPlantManager::g_maxMove[4] = {3, 3, 3, 3};
@ -514,16 +514,16 @@ MxU32 LegoPlantManager::GetAnimationId(LegoEntity* p_entity)
// FUNCTION: LEGO1 0x10026ba0
// FUNCTION: BETA10 0x100c61ba
MxU32 LegoPlantManager::GetSoundId(LegoEntity* p_entity, MxBool p_state)
MxU32 LegoPlantManager::GetSoundId(LegoEntity* p_entity, MxBool p_basedOnMood)
{
LegoPlantInfo* info = GetInfo(p_entity);
if (p_state) {
return (info->m_mood & 1) + g_unk0x100f3164;
if (p_basedOnMood) {
return (info->m_mood & 1) + g_plantSoundIdMoodOffset;
}
if (info != NULL) {
return info->m_sound + g_unk0x100f3160;
return info->m_sound + g_plantSoundIdOffset;
}
return 0;

View File

@ -16,6 +16,8 @@
DECOMP_SIZE_ASSERT(MxTransitionManager, 0x900)
MxTransitionManager::TransitionType g_transitionManagerConfig = MxTransitionManager::e_mosaic;
// GLOBAL: LEGO1 0x100f4378
RECT g_fullScreenRect = {0, 0, 640, 480};
@ -105,7 +107,7 @@ MxResult MxTransitionManager::StartTransition(
backgroundAudioManager->Stop();
}
m_mode = p_animationType;
m_mode = g_transitionManagerConfig;
m_copyFlags.m_bit0 = p_doCopy;
@ -632,3 +634,8 @@ void MxTransitionManager::SetupCopyRect(LPDDSURFACEDESC p_ddsc)
);
}
}
void MxTransitionManager::configureMxTransitionManager(TransitionType p_transitionManagerConfig)
{
g_transitionManagerConfig = p_transitionManagerConfig;
}

View File

@ -20,7 +20,7 @@ LegoActor::LegoActor()
m_frequencyFactor = 0.0f;
m_sound = NULL;
m_unk0x70 = 0.0f;
m_unk0x10 = 0;
m_interaction = 0;
m_actorId = 0;
}

View File

@ -31,7 +31,7 @@ void LegoEntity::Init()
m_roi = NULL;
m_cameraFlag = FALSE;
m_siFile = NULL;
m_unk0x10 = 0;
m_interaction = 0;
m_flags = 0;
m_actionType = Extra::ActionType::e_unknown;
m_targetEntityId = -1;
@ -266,23 +266,23 @@ void LegoEntity::ParseAction(char* p_extra)
// FUNCTION: LEGO1 0x10010f10
// FUNCTION: BETA10 0x1007ee87
void LegoEntity::ClickSound(MxBool p_und)
void LegoEntity::ClickSound(MxBool p_basedOnMood)
{
if (!GetUnknown0x10IsSet(c_altBit1)) {
if (!IsInteraction(c_disabled)) {
MxU32 objectId = 0;
const char* name = m_roi->GetName();
switch (m_type) {
case e_actor:
objectId = CharacterManager()->GetSoundId(m_roi, p_und);
objectId = CharacterManager()->GetSoundId(m_roi, p_basedOnMood);
break;
case e_unk1:
break;
case e_plant:
objectId = PlantManager()->GetSoundId(this, p_und);
objectId = PlantManager()->GetSoundId(this, p_basedOnMood);
break;
case e_building:
objectId = BuildingManager()->GetSoundId(this, p_und);
objectId = BuildingManager()->GetSoundId(this, p_basedOnMood);
break;
}
@ -300,7 +300,7 @@ void LegoEntity::ClickSound(MxBool p_und)
// FUNCTION: BETA10 0x1007f062
void LegoEntity::ClickAnimation()
{
if (!GetUnknown0x10IsSet(c_altBit1)) {
if (!IsInteraction(c_disabled)) {
MxU32 objectId = 0;
MxDSAction action;
const char* name = m_roi->GetName();
@ -332,7 +332,7 @@ void LegoEntity::ClickAnimation()
action.SetObjectId(objectId);
action.AppendExtra(strlen(extra) + 1, extra);
LegoOmni::GetInstance()->GetAnimationManager()->StartEntityAction(action, this);
m_unk0x10 |= c_altBit1;
m_interaction |= c_disabled;
}
}
}

View File

@ -697,7 +697,7 @@ MxLong LegoNavController::Notify(MxParam& p_param)
for (MxS32 i = 0; i < numPlants; i++) {
LegoEntity* entity = plantMgr->CreatePlant(i, NULL, LegoOmni::e_act1);
if (entity != NULL && !entity->GetUnknown0x10IsSet(LegoEntity::c_altBit1)) {
if (entity != NULL && !entity->IsInteraction(LegoEntity::c_disabled)) {
LegoROI* roi = entity->GetROI();
if (roi != NULL && roi->GetVisibility()) {

View File

@ -793,7 +793,7 @@ void LegoAnimPresenter::StartingTickle()
}
FUN_10069b10();
FUN_1006c8a0(TRUE);
SetDisabled(TRUE);
if (m_unk0x78 == NULL) {
if (fabs(m_action->GetDirection()[0]) >= 0.00000047683716F ||
@ -1093,7 +1093,7 @@ void LegoAnimPresenter::EndAction()
}
}
FUN_1006c8a0(FALSE);
SetDisabled(FALSE);
FUN_1006ab70();
VTable0x90();
@ -1154,18 +1154,18 @@ void LegoAnimPresenter::VTable0x90()
}
// FUNCTION: LEGO1 0x1006c8a0
void LegoAnimPresenter::FUN_1006c8a0(MxBool p_bool)
void LegoAnimPresenter::SetDisabled(MxBool p_disabled)
{
if (m_roiMapSize != 0 && m_roiMap != NULL) {
for (MxU32 i = 1; i <= m_roiMapSize; i++) {
LegoEntity* entity = m_roiMap[i]->GetEntity();
if (entity != NULL) {
if (p_bool) {
entity->SetUnknown0x10Flag(LegoEntity::c_altBit1);
if (p_disabled) {
entity->SetInteractionFlag(LegoEntity::c_disabled);
}
else {
entity->ClearUnknown0x10Flag(LegoEntity::c_altBit1);
entity->ClearInteractionFlag(LegoEntity::c_disabled);
}
}
}

View File

@ -108,7 +108,7 @@ MxResult LegoVideoManager::Create(MxVideoParam& p_videoParam, MxU32 p_frequencyM
goto done;
}
if (deviceEnumerate.DoEnumerate() != SUCCESS) {
if (deviceEnumerate.DoEnumerate(hwnd) != SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "LegoDeviceEnumerate::DoEnumerate failed");
goto done;
}

View File

@ -48,7 +48,7 @@ Hospital::Hospital()
m_flashingLeds = 0;
m_copLedAnimTimer = 0;
m_pizzaLedAnimTimer = 0;
m_unk0x128 = 0;
m_exited = FALSE;
NotificationManager()->Register(this);
}
@ -369,8 +369,8 @@ MxLong Hospital::HandleEndAction(MxEndActionNotificationParam& p_param)
act1State = (Act1State*) GameState()->GetState("Act1State");
act1State->SetUnknown18(9);
case HospitalState::e_exitToFront:
if (m_unk0x128 == 0) {
m_unk0x128 = 1;
if (m_exited == FALSE) {
m_exited = TRUE;
m_destLocation = LegoGameState::e_hospitalExited;
DeleteObjects(&m_atomId, HospitalScript::c_hho002cl_RunAnim, HospitalScript::c_hho006cl_RunAnim);
@ -378,8 +378,8 @@ MxLong Hospital::HandleEndAction(MxEndActionNotificationParam& p_param)
}
break;
case HospitalState::e_exitToInfocenter:
if (m_unk0x128 == 0) {
m_unk0x128 = 1;
if (m_exited == FALSE) {
m_exited = TRUE;
m_destLocation = LegoGameState::e_infomain;
DeleteObjects(&m_atomId, HospitalScript::c_hho002cl_RunAnim, HospitalScript::c_hho006cl_RunAnim);
@ -412,8 +412,8 @@ MxLong Hospital::HandleButtonDown(LegoControlManagerNotificationParam& p_param)
m_interactionMode = 3;
if (m_hospitalState->m_state == HospitalState::e_explainQuestShort) {
if (m_unk0x128 == 0) {
m_unk0x128 = 1;
if (m_exited == FALSE) {
m_exited = TRUE;
TickleManager()->UnregisterClient(this);
@ -568,8 +568,8 @@ MxBool Hospital::HandleControl(LegoControlManagerNotificationParam& p_param)
m_currentAction = HospitalScript::c_hho016cl_RunAnim;
m_setWithCurrentAction = 1;
}
else if (m_unk0x128 == 0) {
m_unk0x128 = 1;
else if (m_exited == FALSE) {
m_exited = TRUE;
m_hospitalState->m_state = HospitalState::e_exitImmediately;
m_destLocation = LegoGameState::e_infomain;
@ -589,8 +589,8 @@ MxBool Hospital::HandleControl(LegoControlManagerNotificationParam& p_param)
m_currentAction = HospitalScript::c_hho016cl_RunAnim;
m_setWithCurrentAction = 1;
}
else if (m_unk0x128 == 0) {
m_unk0x128 = 1;
else if (m_exited == FALSE) {
m_exited = TRUE;
m_hospitalState->m_state = HospitalState::e_exitImmediately;
m_destLocation = LegoGameState::e_hospitalExited;

View File

@ -216,27 +216,33 @@ BOOL MxDeviceEnumerate::EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc
BuildErrorString("DirectDraw Create failed: %s\n", EnumerateErrorToString(result));
}
else {
newDevice.m_ddCaps.dwSize = sizeof(newDevice.m_ddCaps);
result = lpDD->GetCaps(&newDevice.m_ddCaps, NULL);
result = lpDD->SetCooperativeLevel(m_hWnd, DDSCL_NORMAL);
if (result != DD_OK) {
BuildErrorString("GetCaps failed: %s\n", EnumerateErrorToString(result));
BuildErrorString("SetCooperativeLevel failed: %s\n", EnumerateErrorToString(result));
}
else {
result = lpDD->QueryInterface(IID_IDirect3D2, (LPVOID*) &lpDirect3d2);
newDevice.m_ddCaps.dwSize = sizeof(newDevice.m_ddCaps);
result = lpDD->GetCaps(&newDevice.m_ddCaps, NULL);
if (result != DD_OK) {
BuildErrorString("D3D creation failed: %s\n", EnumerateErrorToString(result));
BuildErrorString("GetCaps failed: %s\n", EnumerateErrorToString(result));
}
else {
result = lpDirect3d2->EnumDevices(DevicesEnumerateCallback, this);
result = lpDD->QueryInterface(IID_IDirect3D2, (LPVOID*) &lpDirect3d2);
if (result != DD_OK) {
BuildErrorString("D3D enum devices failed: %s\n", EnumerateErrorToString(result));
BuildErrorString("D3D creation failed: %s\n", EnumerateErrorToString(result));
}
else {
if (!newDevice.m_devices.size()) {
m_list.pop_back();
result = lpDirect3d2->EnumDevices(DevicesEnumerateCallback, this);
if (result != DD_OK) {
BuildErrorString("D3D enum devices failed: %s\n", EnumerateErrorToString(result));
}
else {
if (!newDevice.m_devices.size()) {
m_list.pop_back();
}
}
}
}
@ -306,12 +312,14 @@ HRESULT MxDeviceEnumerate::EnumDevicesCallback(
// FUNCTION: CONFIG 0x00401dc0
// FUNCTION: LEGO1 0x1009c6c0
// FUNCTION: BETA10 0x1011e3fa
int MxDeviceEnumerate::DoEnumerate()
int MxDeviceEnumerate::DoEnumerate(HWND hWnd)
{
if (IsInitialized()) {
return -1;
}
m_hWnd = hWnd;
HRESULT ret = DirectDrawEnumerate(DirectDrawEnumerateCallback, this);
if (ret != DD_OK) {
BuildErrorString("DirectDrawEnumerate returned error %s\n", EnumerateErrorToString(ret));

View File

@ -194,7 +194,7 @@ class MxDeviceEnumerate {
MxDeviceEnumerate();
~MxDeviceEnumerate();
virtual int DoEnumerate(); // vtable+0x00
virtual int DoEnumerate(HWND hWnd); // vtable+0x00
BOOL EnumDirectDrawCallback(LPGUID p_guid, LPSTR p_driverDesc, LPSTR p_driverName);
HRESULT EnumDevicesCallback(
@ -242,6 +242,7 @@ class MxDeviceEnumerate {
protected:
list<MxDriver> m_list; // 0x04
unsigned char m_initialized; // 0x10
HWND m_hWnd;
};
// TEMPLATE: BETA10 0x1011c1b0

View File

@ -926,7 +926,7 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::VTable0x44(
transparentColor = RGB555_CREATE(0x1f, 0, 0x1f);
break;
default:
transparentColor = RGB8888_CREATE(0xff, 0, 0xff, 0);
transparentColor = RGB8888_CREATE(0, 0, 0, 0);
break;
}
@ -971,25 +971,11 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::VTable0x44(
surfacePtr += adjustedPitch;
}
if (p_transparent && surface) {
DDCOLORKEY key;
key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = transparentColor;
surface->SetColorKey(DDCKEY_SRCBLT, &key);
}
surface->Unlock(ddsd.lpSurface);
if (p_transparent && surface) {
if (p_transparent && surface && bytesPerPixel != 4) {
DDCOLORKEY key;
if (bytesPerPixel == 1) {
key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = 0;
}
else if (bytesPerPixel == 2) {
key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = RGB555_CREATE(0x1f, 0, 0x1f);
}
else {
key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = RGB8888_CREATE(0xff, 0, 0xff, 0);
}
key.dwColorSpaceLowValue = key.dwColorSpaceHighValue = transparentColor;
surface->SetColorKey(DDCKEY_SRCBLT, &key);
}
}

View File

@ -16,6 +16,7 @@ Please note: this project is dedicated to achieving platform independence withou
| Linux | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
| macOS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
| [Web](https://isle.pizza) | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
| Nintendo 3DS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
We are actively working to support more platforms. If you have experience with a particular platform, we encourage you to contribute to `isle-portable`. You can find a [list of ongoing efforts](https://github.com/isledecomp/isle-portable/wiki/Work%E2%80%90in%E2%80%90progress-ports) in our Wiki.

156
cmake/detectcpu.cmake Normal file
View File

@ -0,0 +1,156 @@
function(DetectTargetCPUArchitectures DETECTED_ARCHS)
set(known_archs EMSCRIPTEN ARM32 ARM64 ARM64EC LOONGARCH64 POWERPC32 POWERPC64 X86 X64)
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
foreach(known_arch IN LISTS known_archs)
set(CPU_${known_arch} "0" PARENT_SCOPE)
endforeach()
set(detected_archs)
foreach(osx_arch IN LISTS CMAKE_OSX_ARCHITECTURES)
if(osx_arch STREQUAL "x86_64")
set(CPU_X64 "1" PARENT_SCOPE)
list(APPEND detected_archs "X64")
elseif(osx_arch STREQUAL "arm64")
set(CPU_ARM64 "1" PARENT_SCOPE)
list(APPEND detected_archs "ARM64")
endif()
endforeach()
set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE)
return()
endif()
set(detected_archs)
foreach(known_arch IN LISTS known_archs)
if(CPU_${known_arch})
list(APPEND detected_archs "${known_arch}")
endif()
endforeach()
if(detected_archs)
set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE)
return()
endif()
set(arch_check_ARM32 "defined(__arm__) || defined(_M_ARM)")
set(arch_check_ARM64 "defined(__aarch64__) || defined(_M_ARM64)")
set(arch_check_ARM64EC "defined(_M_ARM64EC)")
set(arch_check_EMSCRIPTEN "defined(__EMSCRIPTEN__)")
set(arch_check_LOONGARCH64 "defined(__loongarch64)")
set(arch_check_POWERPC32 "(defined(__PPC__) || defined(__powerpc__)) && !defined(__powerpc64__)")
set(arch_check_POWERPC64 "defined(__PPC64__) || defined(__powerpc64__)")
set(arch_check_X86 "defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__) ||defined( __i386) || defined(_M_IX86)")
set(arch_check_X64 "(defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)) && !defined(_M_ARM64EC)")
set(src_vars "")
set(src_main "")
foreach(known_arch IN LISTS known_archs)
set(detected_${known_arch} "0")
string(APPEND src_vars "
#if ${arch_check_${known_arch}}
#define ARCH_${known_arch} \"1\"
#else
#define ARCH_${known_arch} \"0\"
#endif
const char *arch_${known_arch} = \"INFO<${known_arch}=\" ARCH_${known_arch} \">\";
")
string(APPEND src_main "
result += arch_${known_arch}[argc];")
endforeach()
set(src_arch_detect "${src_vars}
int main(int argc, char *argv[]) {
int result = 0;
(void)argv;
${src_main}
return result;
}")
if(CMAKE_C_COMPILER)
set(ext ".c")
elseif(CMAKE_CXX_COMPILER)
set(ext ".cpp")
else()
enable_language(C)
set(ext ".c")
endif()
set(path_src_arch_detect "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/detect_arch${ext}")
file(WRITE "${path_src_arch_detect}" "${src_arch_detect}")
set(path_dir_arch_detect "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/detect_arch")
set(path_bin_arch_detect "${path_dir_arch_detect}/bin")
set(detected_archs)
set(msg "Detecting Target CPU Architecture")
message(STATUS "${msg}")
include(CMakePushCheckState)
set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")
cmake_push_check_state(RESET)
try_compile(CPU_CHECK_ALL
"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/CMakeTmp/detect_arch"
SOURCES "${path_src_arch_detect}"
COPY_FILE "${path_bin_arch_detect}"
)
cmake_pop_check_state()
if(NOT CPU_CHECK_ALL)
message(STATUS "${msg} - <ERROR>")
message(WARNING "Failed to compile source detecting the target CPU architecture")
else()
set(re "INFO<([A-Z0-9]+)=([01])>")
file(STRINGS "${path_bin_arch_detect}" infos REGEX "${re}")
foreach(info_arch_01 IN LISTS infos)
string(REGEX MATCH "${re}" A "${info_arch_01}")
if(NOT "${CMAKE_MATCH_1}" IN_LIST known_archs)
message(WARNING "Unknown architecture: \"${CMAKE_MATCH_1}\"")
continue()
endif()
set(arch "${CMAKE_MATCH_1}")
set(arch_01 "${CMAKE_MATCH_2}")
set(detected_${arch} "${arch_01}")
endforeach()
foreach(known_arch IN LISTS known_archs)
if(detected_${known_arch})
list(APPEND detected_archs ${known_arch})
endif()
endforeach()
endif()
if(detected_archs)
foreach(known_arch IN LISTS known_archs)
set("CPU_${known_arch}" "${detected_${known_arch}}" CACHE BOOL "Detected architecture ${known_arch}")
endforeach()
message(STATUS "${msg} - ${detected_archs}")
else()
include(CheckCSourceCompiles)
cmake_push_check_state(RESET)
foreach(known_arch IN LISTS known_archs)
if(NOT detected_archs)
set(cache_variable "CPU_${known_arch}")
set(test_src "
int main(int argc, char *argv[]) {
#if ${arch_check_${known_arch}}
return 0;
#else
choke
#endif
}
")
check_c_source_compiles("${test_src}" "${cache_variable}")
if(${cache_variable})
set(CPU_${known_arch} "1" CACHE BOOL "Detected architecture ${known_arch}")
set(detected_archs ${known_arch})
else()
set(CPU_${known_arch} "0" CACHE BOOL "Detected architecture ${known_arch}")
endif()
endif()
endforeach()
cmake_pop_check_state()
endif()
set("${DETECTED_ARCHS}" "${detected_archs}" PARENT_SCOPE)
endfunction()

View File

@ -32,14 +32,16 @@ target_compile_definitions(miniwin PRIVATE
)
find_package(OpenGL)
find_package(GLEW)
if(OpenGL_FOUND AND GLEW_FOUND)
message(STATUS "Found OpenGL and GLEW: enabling OpenGL 1.x renderer")
target_sources(miniwin PRIVATE src/d3drm/backends/opengl1/renderer.cpp)
if(OpenGL_FOUND)
message(STATUS "Found OpenGL: enabling OpenGL 1.x renderer")
target_sources(miniwin PRIVATE
src/d3drm/backends/opengl1/actual.cpp
src/d3drm/backends/opengl1/renderer.cpp
)
target_compile_definitions(miniwin PRIVATE USE_OPENGL1)
target_link_libraries(miniwin PRIVATE OpenGL::GL GLEW::GLEW)
target_link_libraries(miniwin PRIVATE OpenGL::GL)
else()
message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL and GLEW")
message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL")
endif()
find_library(OPENGL_ES2_LIBRARY NAMES GLESv2)
@ -52,6 +54,23 @@ else()
message(STATUS "🧩 OpenGL ES 2.x support not enabled")
endif()
if(NINTENDO_3DS)
if(ISLE_DEBUG)
find_library(CITRO3D_LIBRARY NAMES citro3dd)
else()
find_library(CITRO3D_LIBRARY NAMES citro3d)
endif()
if(CITRO3D_LIBRARY)
message(STATUS "Found citro3d: enabling Citro3D renderer")
target_sources(miniwin PRIVATE src/d3drm/backends/citro3d/renderer.cpp)
ctr_add_shader_library(vshader src/d3drm/backends/citro3d/vshader.v.pica)
dkp_add_embedded_binary_library(3ds_shaders vshader)
target_link_libraries(miniwin PRIVATE ${CITRO3D_LIBRARY} 3ds_shaders)
else()
message(STATUS "🧩 Citro3D support not enabled")
endif()
endif()
if(WIN32)
target_sources(miniwin PRIVATE
src/d3drm/backends/directx9/actual.cpp

View File

@ -0,0 +1,606 @@
#include "d3drmrenderer.h"
#include "d3drmrenderer_citro3d.h"
#include "d3drmtexture_impl.h"
#include "ddraw_impl.h"
#include "meshutils.h"
#include "miniwin.h"
#include "vshader_shbin.h"
#include <cstring>
static bool g_rendering = false;
static DVLB_s* vshader_dvlb;
static shaderProgram_s program;
static int uLoc_projection;
static int uLoc_modelView;
static int uLoc_meshColor;
static int uLoc_lightVec;
static int uLoc_lightClr;
static int uLoc_shininess;
Citro3DRenderer::Citro3DRenderer(DWORD width, DWORD height)
{
m_width = 320;
m_height = 240;
m_virtualWidth = width;
m_virtualHeight = height;
gfxInitDefault();
consoleInit(GFX_TOP, nullptr);
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
m_renderTarget = C3D_RenderTargetCreate(m_height, m_width, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8);
C3D_RenderTargetSetOutput(
m_renderTarget,
GFX_BOTTOM,
GFX_LEFT,
GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) |
GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) |
GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)
);
vshader_dvlb = DVLB_ParseFile((u32*) vshader_shbin, vshader_shbin_size);
shaderProgramInit(&program);
shaderProgramSetVsh(&program, &vshader_dvlb->DVLE[0]);
C3D_BindProgram(&program);
C3D_CullFace(GPU_CULL_FRONT_CCW);
uLoc_projection = shaderInstanceGetUniformLocation(program.vertexShader, "projection");
uLoc_modelView = shaderInstanceGetUniformLocation(program.vertexShader, "modelView");
uLoc_meshColor = shaderInstanceGetUniformLocation(program.vertexShader, "meshColor");
uLoc_lightVec = shaderInstanceGetUniformLocation(program.vertexShader, "lightVec");
uLoc_lightClr = shaderInstanceGetUniformLocation(program.vertexShader, "lightClr");
uLoc_shininess = shaderInstanceGetUniformLocation(program.vertexShader, "shininess");
C3D_AttrInfo* attrInfo = C3D_GetAttrInfo();
AttrInfo_Init(attrInfo);
AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position
AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 3); // v2=normal
AttrInfo_AddLoader(attrInfo, 2, GPU_FLOAT, 2); // v1=texcoord
}
Citro3DRenderer::~Citro3DRenderer()
{
shaderProgramFree(&program);
DVLB_Free(vshader_dvlb);
C3D_Fini();
gfxExit();
}
void Citro3DRenderer::PushLights(const SceneLight* lights, size_t count)
{
m_lights.assign(lights, lights + count);
}
void Citro3DRenderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back)
{
memcpy(&m_projection, projection, sizeof(D3DRMMATRIX4D));
}
void Citro3DRenderer::SetFrustumPlanes(const Plane* frustumPlanes)
{
}
struct Citro3DCacheDestroyContext {
Citro3DRenderer* renderer;
Uint32 id;
};
void Citro3DRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture)
{
auto* ctx = new Citro3DCacheDestroyContext{this, id};
texture->AddDestroyCallback(
[](IDirect3DRMObject* obj, void* arg) {
auto* ctx = static_cast<Citro3DCacheDestroyContext*>(arg);
auto& entry = ctx->renderer->m_textures[ctx->id];
if (entry.texture) {
C3D_TexDelete(&entry.c3dTex);
entry.texture = nullptr;
}
delete ctx;
},
ctx
);
}
static int NearestPowerOfTwoClamp(int val)
{
static const int sizes[] = {8, 16, 32, 64, 128, 256, 512};
for (int size : sizes) {
if (val <= size) {
return size;
}
}
return 512;
}
static SDL_Surface* ConvertAndResizeSurface(SDL_Surface* original, bool isUI, float scale)
{
SDL_Surface* converted = SDL_ConvertSurface(original, SDL_PIXELFORMAT_RGBA8888);
if (!converted) {
return nullptr;
}
if (!isUI) {
return converted;
}
int scaledW = static_cast<int>(converted->w * scale);
int scaledH = static_cast<int>(converted->h * scale);
int paddedW = NearestPowerOfTwoClamp(scaledW);
int paddedH = NearestPowerOfTwoClamp(scaledH);
SDL_Surface* padded = SDL_CreateSurface(paddedW, paddedH, SDL_PIXELFORMAT_RGBA8888);
if (!padded) {
SDL_DestroySurface(converted);
return nullptr;
}
SDL_Rect dstRect = {0, 0, scaledW, scaledH};
SDL_BlitSurfaceScaled(converted, nullptr, padded, &dstRect, SDL_SCALEMODE_LINEAR);
SDL_DestroySurface(converted);
return padded;
}
static void EncodeTextureLayout(const u8* src, u8* dst, int width, int height)
{
const int tileSize = 8;
const int bytesPerPixel = 4;
int tilesPerRow = (width + tileSize - 1) / tileSize;
static const uint8_t mortonLUT[64] = {0, 1, 4, 5, 16, 17, 20, 21, 2, 3, 6, 7, 18, 19, 22, 23,
8, 9, 12, 13, 24, 25, 28, 29, 10, 11, 14, 15, 26, 27, 30, 31,
32, 33, 36, 37, 48, 49, 52, 53, 34, 35, 38, 39, 50, 51, 54, 55,
40, 41, 44, 45, 56, 57, 60, 61, 42, 43, 46, 47, 58, 59, 62, 63};
for (int tileY = 0; tileY < height; tileY += tileSize) {
for (int tileX = 0; tileX < width; tileX += tileSize) {
int tileIndex = (tileY / tileSize) * tilesPerRow + (tileX / tileSize);
tileIndex *= tileSize * tileSize;
for (int y = 0; y < tileSize; ++y) {
for (int x = 0; x < tileSize; ++x) {
int srcX = tileX + x;
int srcY = tileY + y;
if (srcX >= width || srcY >= height) {
continue;
}
int morton = mortonLUT[y * tileSize + x];
int dstIndex = (tileIndex + morton) * bytesPerPixel;
int srcIndex = ((height - 1 - srcY) * width + srcX);
*(u32*) &dst[dstIndex] = ((u32*) src)[srcIndex];
}
}
}
}
}
static bool ConvertAndUploadTexture(C3D_Tex* tex, SDL_Surface* originalSurface, bool isUI, float scale)
{
SDL_Surface* resized = ConvertAndResizeSurface(originalSurface, isUI, scale);
if (!resized) {
return false;
}
int width = resized->w;
int height = resized->h;
C3D_TexInitParams params = {};
params.width = width;
params.height = height;
params.format = GPU_RGBA8;
params.maxLevel = isUI ? 0 : 4;
params.type = GPU_TEX_2D;
if (!C3D_TexInitWithParams(tex, nullptr, params)) {
SDL_DestroySurface(resized);
return false;
}
uint8_t* tiledData = (uint8_t*) malloc(width * height * 4);
if (!tiledData) {
SDL_DestroySurface(resized);
return false;
}
EncodeTextureLayout((const u8*) resized->pixels, tiledData, width, height);
SDL_DestroySurface(resized);
C3D_TexUpload(tex, tiledData);
free(tiledData);
if (isUI) {
C3D_TexSetFilter(tex, GPU_NEAREST, GPU_NEAREST);
C3D_TexSetWrap(tex, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE);
}
else {
C3D_TexSetFilter(tex, GPU_LINEAR, GPU_LINEAR);
C3D_TexSetWrap(tex, GPU_REPEAT, GPU_REPEAT);
C3D_TexSetFilterMipmap(tex, GPU_LINEAR);
C3D_TexGenerateMipmap(tex, GPU_TEXFACE_2D);
}
return true;
}
Uint32 Citro3DRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
SDL_Surface* originalSurface = surface->m_surface;
int originalW = originalSurface->w;
int originalH = originalSurface->h;
for (Uint32 i = 0; i < m_textures.size(); ++i) {
auto& tex = m_textures[i];
if (tex.texture == texture) {
if (tex.version != texture->m_version) {
C3D_TexDelete(&tex.c3dTex);
if (!ConvertAndUploadTexture(&tex.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) {
return NO_TEXTURE_ID;
}
tex.version = texture->m_version;
tex.width = NearestPowerOfTwoClamp(originalW * m_viewportTransform.scale);
tex.height = NearestPowerOfTwoClamp(originalH * m_viewportTransform.scale);
}
return i;
}
}
C3DTextureCacheEntry entry;
entry.texture = texture;
entry.version = texture->m_version;
entry.width = NearestPowerOfTwoClamp(originalW * m_viewportTransform.scale);
entry.height = NearestPowerOfTwoClamp(originalH * m_viewportTransform.scale);
if (!ConvertAndUploadTexture(&entry.c3dTex, originalSurface, isUi, m_viewportTransform.scale)) {
return NO_TEXTURE_ID;
}
for (Uint32 i = 0; i < m_textures.size(); ++i) {
if (!m_textures[i].texture) {
m_textures[i] = std::move(entry);
AddTextureDestroyCallback(i, texture);
return i;
}
}
m_textures.push_back(std::move(entry));
AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture);
return (Uint32) (m_textures.size() - 1);
}
C3DMeshCacheEntry C3DUploadMesh(const MeshGroup& meshGroup)
{
C3DMeshCacheEntry cache{&meshGroup, meshGroup.version};
std::vector<D3DRMVERTEX> vertexBuffer;
std::vector<uint16_t> indexBuffer;
if (meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT) {
FlattenSurfaces(
meshGroup.vertices.data(),
meshGroup.vertices.size(),
meshGroup.indices.data(),
meshGroup.indices.size(),
meshGroup.texture != nullptr,
vertexBuffer,
indexBuffer
);
}
else {
vertexBuffer.assign(meshGroup.vertices.begin(), meshGroup.vertices.end());
indexBuffer.assign(meshGroup.indices.begin(), meshGroup.indices.end());
}
// Flatten vertices as IBO is buggy on 3DS hardware
std::vector<D3DRMVERTEX> vertexUploadBuffer;
vertexUploadBuffer.reserve(indexBuffer.size());
for (size_t i = 0; i < indexBuffer.size(); ++i) {
vertexUploadBuffer.emplace_back(vertexBuffer[indexBuffer[i]]);
}
size_t vertexBufferSize = vertexUploadBuffer.size() * sizeof(D3DRMVERTEX);
cache.vbo = linearAlloc(vertexBufferSize);
memcpy(cache.vbo, vertexUploadBuffer.data(), vertexBufferSize);
cache.vertexCount = vertexUploadBuffer.size();
return cache;
}
void Citro3DRenderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh)
{
auto* ctx = new Citro3DCacheDestroyContext{this, id};
mesh->AddDestroyCallback(
[](IDirect3DRMObject* obj, void* arg) {
auto* ctx = static_cast<Citro3DCacheDestroyContext*>(arg);
auto& cacheEntry = ctx->renderer->m_meshs[ctx->id];
if (cacheEntry.meshGroup) {
cacheEntry.meshGroup = nullptr;
linearFree(cacheEntry.vbo);
cacheEntry.vertexCount = 0;
}
delete ctx;
},
ctx
);
}
Uint32 Citro3DRenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup)
{
for (Uint32 i = 0; i < m_meshs.size(); ++i) {
auto& cache = m_meshs[i];
if (cache.meshGroup == meshGroup) {
if (cache.version != meshGroup->version) {
cache = std::move(C3DUploadMesh(*meshGroup));
}
return i;
}
}
auto newCache = C3DUploadMesh(*meshGroup);
for (Uint32 i = 0; i < m_meshs.size(); ++i) {
auto& cache = m_meshs[i];
if (!cache.meshGroup) {
cache = std::move(newCache);
AddMeshDestroyCallback(i, mesh);
return i;
}
}
m_meshs.push_back(std::move(newCache));
AddMeshDestroyCallback((Uint32) (m_meshs.size() - 1), mesh);
return (Uint32) (m_meshs.size() - 1);
}
void Citro3DRenderer::StartFrame()
{
if (g_rendering) {
return;
}
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
C3D_FrameDrawOn(m_renderTarget);
g_rendering = true;
}
void ConvertPerspective(const D3DRMMATRIX4D in, C3D_Mtx* out)
{
float f_h = in[0][0];
float f_v = in[1][1];
float aspect = f_v / f_h;
float fovY = 2.0f * atanf(1.0f / f_v);
float nearZ = -in[3][2] / in[2][2];
float farZ = nearZ * in[2][2] / (in[2][2] - 1.0f);
Mtx_PerspTilt(out, fovY, aspect, nearZ, farZ, true);
}
HRESULT Citro3DRenderer::BeginFrame()
{
StartFrame();
C3D_DepthTest(true, GPU_GREATER, GPU_WRITE_ALL);
C3D_Mtx projection;
ConvertPerspective(m_projection, &projection);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection);
for (const auto& light : m_lights) {
FColor lightColor = light.color;
if (light.positional == 0.0f && light.directional == 0.0f) {
// Ambient light
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 2, lightColor.r, lightColor.g, lightColor.b, 1.0f);
}
else if (light.directional == 1.0f) {
C3D_FVUnifSet(
GPU_VERTEX_SHADER,
uLoc_lightVec + 1,
-light.direction.x,
-light.direction.y,
-light.direction.z,
0.0f
);
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 1, lightColor.r, lightColor.g, lightColor.b, 0.0f);
}
else if (light.positional == 1.0f) {
C3D_FVUnifSet(
GPU_VERTEX_SHADER,
uLoc_lightVec + 0,
light.position.x,
light.position.y,
light.position.z,
0.0f
);
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 0, lightColor.r, lightColor.g, lightColor.b, 0.0f);
}
}
return S_OK;
}
void Citro3DRenderer::EnableTransparency()
{
C3D_DepthTest(true, GPU_GREATER, GPU_WRITE_COLOR);
}
void ConvertMatrix(const D3DRMMATRIX4D in, C3D_Mtx* out)
{
for (int i = 0; i < 4; i++) {
out->r[i].x = in[0][i];
out->r[i].y = in[1][i];
out->r[i].z = in[2][i];
out->r[i].w = in[3][i];
}
}
void Citro3DRenderer::SubmitDraw(
DWORD meshId,
const D3DRMMATRIX4D& modelViewMatrix,
const D3DRMMATRIX4D& worldMatrix,
const D3DRMMATRIX4D& viewMatrix,
const Matrix3x3& normalMatrix,
const Appearance& appearance
)
{
C3D_Mtx modelView;
ConvertMatrix(modelViewMatrix, &modelView);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_modelView, &modelView);
auto& mesh = m_meshs[meshId];
C3D_BufInfo* bufInfo = C3D_GetBufInfo();
BufInfo_Init(bufInfo);
BufInfo_Add(bufInfo, mesh.vbo, sizeof(D3DRMVERTEX), 3, 0x210);
C3D_FVUnifSet(
GPU_VERTEX_SHADER,
uLoc_meshColor,
appearance.color.r / 255.0f,
appearance.color.g / 255.0f,
appearance.color.b / 255.0f,
appearance.color.a / 255.0f
);
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, appearance.shininess / 255.0f, 0.0f, 0.0f, 0.0f);
if (appearance.textureId != NO_TEXTURE_ID) {
C3D_TexBind(0, &m_textures[appearance.textureId].c3dTex);
C3D_TexEnv* env = C3D_GetTexEnv(0);
C3D_TexEnvInit(env);
C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE);
}
else {
C3D_TexBind(0, nullptr);
C3D_TexEnv* env = C3D_GetTexEnv(0);
C3D_TexEnvInit(env);
C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE);
}
C3D_DrawArrays(GPU_TRIANGLES, 0, mesh.vertexCount);
}
HRESULT Citro3DRenderer::FinalizeFrame()
{
return S_OK;
}
void Citro3DRenderer::Resize(int width, int height, const ViewportTransform& viewportTransform)
{
m_width = width;
m_height = height;
m_viewportTransform = viewportTransform;
}
void Citro3DRenderer::Clear(float r, float g, float b)
{
StartFrame();
u32 color =
(static_cast<u32>(r * 255) << 24) | (static_cast<u32>(g * 255) << 16) | (static_cast<u32>(b * 255) << 8) | 255;
C3D_RenderTargetClear(m_renderTarget, C3D_CLEAR_ALL, color, 0);
}
void Citro3DRenderer::Flip()
{
C3D_FrameEnd(0);
gfxFlushBuffers();
gspWaitForVBlank();
g_rendering = false;
}
void Citro3DRenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect)
{
C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
StartFrame();
C3D_DepthTest(false, GPU_GREATER, GPU_WRITE_COLOR);
float left = -m_viewportTransform.offsetX / m_viewportTransform.scale;
float right = (m_width - m_viewportTransform.offsetX) / m_viewportTransform.scale;
float top = -m_viewportTransform.offsetY / m_viewportTransform.scale;
float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale;
C3D_Mtx projection, modelView;
Mtx_OrthoTilt(&projection, left, right, bottom, top, 0.0f, 1.0f, true);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection);
Mtx_Identity(&modelView);
C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_modelView, &modelView);
// Set light directions
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightVec + 0, 0.0f, 0.0f, 0.0f, 0.0f);
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightVec + 1, 0.0f, 0.0f, 0.0f, 0.0f);
// Set light colors
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 0, 0.0f, 0.0f, 0.0f, 0.0f);
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 1, 0.0f, 0.0f, 0.0f, 0.0f);
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_lightClr + 2, 1.0f, 1.0f, 1.0f, 1.0f); // Ambient
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_shininess, 0.0f, 0.0f, 0.0f, 0.0f);
C3D_FVUnifSet(GPU_VERTEX_SHADER, uLoc_meshColor, 1.0f, 1.0f, 1.0f, 1.0f);
C3DTextureCacheEntry& texture = m_textures[textureId];
C3D_TexBind(0, &texture.c3dTex);
C3D_TexEnv* env = C3D_GetTexEnv(0);
C3D_TexEnvInit(env);
C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, GPU_PRIMARY_COLOR);
C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE);
float scale = m_viewportTransform.scale;
float x1 = static_cast<float>(dstRect.x);
float y1 = static_cast<float>(dstRect.y);
float x2 = x1 + static_cast<float>(dstRect.w);
float y2 = y1 + static_cast<float>(dstRect.h);
float u0 = (srcRect.x * scale) / texture.width;
float u1 = ((srcRect.x + srcRect.w) * scale) / texture.width;
float v0 = (srcRect.y * scale) / texture.height;
float v1 = ((srcRect.y + srcRect.h) * scale) / texture.height;
C3D_ImmDrawBegin(GPU_TRIANGLES);
// Triangle 1
C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f);
C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f);
C3D_ImmSendAttrib(u0, v0, 0.0f, 0.0f);
C3D_ImmSendAttrib(x2, y1, 0.5f, 0.0f);
C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f);
C3D_ImmSendAttrib(u1, v0, 0.0f, 0.0f);
C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f);
C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f);
C3D_ImmSendAttrib(u1, v1, 0.0f, 0.0f);
// Triangle 2
C3D_ImmSendAttrib(x2, y2, 0.5f, 0.0f);
C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f);
C3D_ImmSendAttrib(u1, v1, 0.0f, 0.0f);
C3D_ImmSendAttrib(x1, y2, 0.5f, 0.0f);
C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f);
C3D_ImmSendAttrib(u0, v1, 0.0f, 0.0f);
C3D_ImmSendAttrib(x1, y1, 0.5f, 0.0f);
C3D_ImmSendAttrib(0.0f, 0.0f, 1.0f, 0.0f);
C3D_ImmSendAttrib(u0, v0, 0.0f, 0.0f);
C3D_ImmDrawEnd();
}
void Citro3DRenderer::Download(SDL_Surface* target)
{
MINIWIN_NOT_IMPLEMENTED();
}

View File

@ -0,0 +1,130 @@
; Uniforms
.fvec projection[4], modelView[4], meshColor
.fvec lightVec[2], lightClr[3], shininess
; Constants
.constf myconst(0.0, 1.0, -1.0, -0.5)
; Outputs
.out outpos position
.out outtc0 texcoord0
.out outclr color
; Inputs
.alias inpos v0
.alias innrm v1
.alias intex v2
.proc main
; Prepare constants in usable temp regs
mov r15.x, myconst.x ; 0.0
mov r15.y, myconst.y ; 1.0
mov r15.z, myconst.z ; -1.0
; Force the w component of inpos to be 1.0
mov r0.xyz, inpos
mov r0.w, r15.y
; r1 = modelView * inpos
dp4 r1.x, modelView[0], r0
dp4 r1.y, modelView[1], r0
dp4 r1.z, modelView[2], r0
dp4 r1.w, modelView[3], r0
; outpos = projection * r1
dp4 outpos.x, projection[0], r1
dp4 outpos.y, projection[1], r1
dp4 outpos.z, projection[2], r1
dp4 outpos.w, projection[3], r1
; outtex = intex
mov outtc0, intex
mov outtc0.zw, myconst.xy
; Transform normal
mov r2.xyz, innrm
mov r2.w, r15.x
dp4 r3.x, modelView[0], r2
dp4 r3.y, modelView[1], r2
dp4 r3.z, modelView[2], r2
mov r3.w, r15.x
dp3 r4.x, r3, r3
rsq r4.x, r4.x
mul r3, r4.xxxx, r3 ; r3 = normalized normal
; Normalize lightVec[0]
mov r5, lightVec[0]
dp3 r6.x, r5, r5
rsq r6.x, r6.x
mul r5, r6.xxxx, r5
; dot(normal, lightVec[0])
dp3 r6.x, r3, r5
max r6.x, r6.x, r15.xxxx
; Normalize lightVec[1]
mov r7, lightVec[1]
dp3 r8.x, r7, r7
rsq r8.x, r8.x
mul r7, r8.xxxx, r7
; dot(normal, lightVec[1])
dp3 r6.y, r3, r7
max r6.y, r6.y, r15.xxxx
; Load lightClr
mov r8, lightClr[2] ; ambient
mov r9, lightClr[0] ; point
mov r10, lightClr[1] ; directional
; diffuse = ambient + (lightClr[0] * dot0) + (lightClr[1] * dot1)
mul r11, r9, r6.xxxx
add r8, r8, r11
mul r11, r10, r6.yyyy
add r8, r8, r11 ; r8 = diffuse
; Check if shininess > 0
mov r12, shininess
slt r13.x, r15.x, r12.x
; viewVec = normalize(-position.xyz)
mov r14.xyz, r1.xyz
mul r14.xyz, r14.xyz, r15.zzz
dp3 r4.x, r14, r14
rsq r4.x, r4.x
mul r14, r4.xxxx, r14
; H = normalize(view + lightVec[1])
add r11, r14, r7
dp3 r4.x, r11, r11
rsq r4.x, r4.x
mul r11, r4.xxxx, r11
; dot(normal, H)
dp3 r4.x, r3, r11
max r4.x, r4.x, r15.x
; Approximate pow(dotNH, 10) by repeated multiplication
mul r5.x, r4.x, r4.x ; dotNH^2
mul r5.x, r5.x, r5.x ; dotNH^4
mul r5.x, r5.x, r5.x ; dotNH^8
mul r4.x, r5.x, r4.x ; dotNH^9
mul r4.x, r4.x, r4.x ; dotNH^10
; Multiply by shininess > 0 flag
mul r4.x, r4.x, r13.x
; specular = lightClr[1] * spec
mul r5, r10, r4.xxxx
; final = diffuse * meshColor + specular * lightClr[1]
mov r9, meshColor
mul r6, r8, r9 ; diffuse * meshColor
add r7.xyz, r6.xyz, r5.xyz ; add specular (already multiplied by lightClr)
min r7.xyz, r7.xyz, r15.yyyy
mov outclr.xyz, r7.xyz
mov outclr.w, meshColor.w
end
.end

View File

@ -76,7 +76,7 @@ void DirectX9Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture*
);
}
Uint32 DirectX9Renderer::GetTextureId(IDirect3DRMTexture* iTexture)
Uint32 DirectX9Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
@ -216,24 +216,6 @@ Uint32 DirectX9Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshG
return static_cast<Uint32>(m_meshs.size() - 1);
}
void DirectX9Renderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc)
{
halDesc->dcmColorModel = D3DCOLORMODEL::RGB;
halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc->dwDeviceZBufferBitDepth = DDBD_24;
helDesc->dwDeviceRenderBitDepth = DDBD_32;
halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
memset(helDesc, 0, sizeof(D3DDEVICEDESC));
}
const char* DirectX9Renderer::GetName()
{
return "DirectX 9 HAL";
}
HRESULT DirectX9Renderer::BeginFrame()
{
return Actual_BeginFrame();

View File

@ -0,0 +1,354 @@
// This file cannot include any minwin headers.
#include "actual.h"
#include "structs.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_opengl.h>
#include <algorithm>
#include <cstring>
#include <vector>
// GL extension API functions.
bool g_useVBOs;
PFNGLGENBUFFERSPROC mwglGenBuffers;
PFNGLBINDBUFFERPROC mwglBindBuffer;
PFNGLBUFFERDATAPROC mwglBufferData;
PFNGLDELETEBUFFERSPROC mwglDeleteBuffers;
void GL11_InitState()
{
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CW);
}
void GL11_LoadExtensions()
{
g_useVBOs = SDL_GL_ExtensionSupported("GL_ARB_vertex_buffer_object");
if (g_useVBOs) {
// Load the required GL function pointers.
mwglGenBuffers = (PFNGLGENBUFFERSPROC) SDL_GL_GetProcAddress("glGenBuffersARB");
mwglBindBuffer = (PFNGLBINDBUFFERPROC) SDL_GL_GetProcAddress("glBindBufferARB");
mwglBufferData = (PFNGLBUFFERDATAPROC) SDL_GL_GetProcAddress("glBufferDataARB");
mwglDeleteBuffers = (PFNGLDELETEBUFFERSPROC) SDL_GL_GetProcAddress("glDeleteBuffersARB");
}
}
void GL11_DestroyTexture(GLuint texId)
{
glDeleteTextures(1, &texId);
}
GLuint GL11_UploadTextureData(void* pixels, int width, int height)
{
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
return texId;
}
void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture)
{
if (g_useVBOs) {
mwglGenBuffers(1, &cache.vboPositions);
mwglBindBuffer(GL_ARRAY_BUFFER_ARB, cache.vboPositions);
mwglBufferData(
GL_ARRAY_BUFFER_ARB,
cache.positions.size() * sizeof(GL11_BridgeVector),
cache.positions.data(),
GL_STATIC_DRAW_ARB
);
mwglGenBuffers(1, &cache.vboNormals);
mwglBindBuffer(GL_ARRAY_BUFFER_ARB, cache.vboNormals);
mwglBufferData(
GL_ARRAY_BUFFER_ARB,
cache.normals.size() * sizeof(GL11_BridgeVector),
cache.normals.data(),
GL_STATIC_DRAW_ARB
);
if (hasTexture) {
mwglGenBuffers(1, &cache.vboTexcoords);
mwglBindBuffer(GL_ARRAY_BUFFER_ARB, cache.vboTexcoords);
mwglBufferData(
GL_ARRAY_BUFFER_ARB,
cache.texcoords.size() * sizeof(GL11_BridgeTexCoord),
cache.texcoords.data(),
GL_STATIC_DRAW_ARB
);
}
mwglGenBuffers(1, &cache.ibo);
mwglBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, cache.ibo);
mwglBufferData(
GL_ELEMENT_ARRAY_BUFFER_ARB,
cache.indices.size() * sizeof(cache.indices[0]),
cache.indices.data(),
GL_STATIC_DRAW_ARB
);
}
}
void GL11_DestroyMesh(GLMeshCacheEntry& cache)
{
if (g_useVBOs) {
mwglDeleteBuffers(1, &cache.vboPositions);
mwglDeleteBuffers(1, &cache.vboNormals);
mwglDeleteBuffers(1, &cache.vboTexcoords);
mwglDeleteBuffers(1, &cache.ibo);
}
}
void GL11_BeginFrame(const Matrix4x4* projection)
{
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glEnable(GL_LIGHTING);
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
// Disable all lights and reset global ambient
for (int i = 0; i < 8; ++i) {
glDisable(GL_LIGHT0 + i);
}
const GLfloat zeroAmbient[4] = {0.f, 0.f, 0.f, 1.f};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, zeroAmbient);
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
// Projection and view
glMatrixMode(GL_PROJECTION);
glLoadMatrixf((const GLfloat*) projection);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void GL11_UploadLight(int lightIdx, GL11_BridgeSceneLight* l)
{
// Setup light
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
GLenum lightId = GL_LIGHT0 + lightIdx++;
const FColor& c = l->color;
GLfloat col[4] = {c.r, c.g, c.b, c.a};
const GLfloat zeroAmbient[4] = {0.f, 0.f, 0.f, 1.f};
if (l->positional == 0.f && l->directional == 0.f) {
// Ambient light only
glLightfv(lightId, GL_AMBIENT, col);
const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f};
glLightfv(lightId, GL_DIFFUSE, black);
glLightfv(lightId, GL_SPECULAR, black);
const GLfloat dummyPos[4] = {0.f, 0.f, 1.f, 0.f};
glLightfv(lightId, GL_POSITION, dummyPos);
}
else {
glLightfv(lightId, GL_AMBIENT, zeroAmbient);
glLightfv(lightId, GL_DIFFUSE, col);
if (l->directional == 1.0f) {
glLightfv(lightId, GL_SPECULAR, col);
}
else {
const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f};
glLightfv(lightId, GL_SPECULAR, black);
}
GLfloat pos[4];
if (l->directional == 1.f) {
pos[0] = -l->direction.x;
pos[1] = -l->direction.y;
pos[2] = -l->direction.z;
pos[3] = 0.f;
}
else {
pos[0] = l->position.x;
pos[1] = l->position.y;
pos[2] = l->position.z;
pos[3] = 1.f;
}
glLightfv(lightId, GL_POSITION, pos);
}
glEnable(lightId);
glPopMatrix();
}
void GL11_EnableTransparency()
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
}
#define NO_TEXTURE_ID 0xffffffff
void GL11_SubmitDraw(
GLMeshCacheEntry& mesh,
const Matrix4x4& modelViewMatrix,
const Appearance& appearance,
GLuint texId
)
{
glLoadMatrixf(&modelViewMatrix[0][0]);
glEnable(GL_NORMALIZE);
glColor4ub(appearance.color.r, appearance.color.g, appearance.color.b, appearance.color.a);
if (appearance.shininess != 0.0f) {
GLfloat whiteSpec[] = {1.f, 1.f, 1.f, 1.f};
glMaterialfv(GL_FRONT, GL_SPECULAR, whiteSpec);
glMaterialf(GL_FRONT, GL_SHININESS, appearance.shininess);
}
else {
GLfloat noSpec[] = {0.0f, 0.0f, 0.0f, 0.0f};
glMaterialfv(GL_FRONT, GL_SPECULAR, noSpec);
glMaterialf(GL_FRONT, GL_SHININESS, 0.0f);
}
if (mesh.flat) {
glShadeModel(GL_FLAT);
}
else {
glShadeModel(GL_SMOOTH);
}
// Bind texture if present
if (appearance.textureId != NO_TEXTURE_ID) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texId);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
else {
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
if (g_useVBOs) {
mwglBindBuffer(GL_ARRAY_BUFFER_ARB, mesh.vboPositions);
glVertexPointer(3, GL_FLOAT, 0, nullptr);
mwglBindBuffer(GL_ARRAY_BUFFER_ARB, mesh.vboNormals);
glNormalPointer(GL_FLOAT, 0, nullptr);
if (appearance.textureId != NO_TEXTURE_ID) {
mwglBindBuffer(GL_ARRAY_BUFFER_ARB, mesh.vboTexcoords);
glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
}
mwglBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, mesh.ibo);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr);
mwglBindBuffer(GL_ARRAY_BUFFER_ARB, 0);
mwglBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}
else {
glVertexPointer(3, GL_FLOAT, 0, mesh.positions.data());
glNormalPointer(GL_FLOAT, 0, mesh.normals.data());
if (appearance.textureId != NO_TEXTURE_ID) {
glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords.data());
}
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()), GL_UNSIGNED_SHORT, mesh.indices.data());
}
glPopMatrix();
}
void GL11_Resize(int width, int height)
{
glViewport(0, 0, width, height);
}
void GL11_Clear(float r, float g, float b)
{
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glClearColor(r, g, b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void GL11_Draw2DImage(
GLuint texId,
const SDL_Rect& srcRect,
const SDL_Rect& dstRect,
float left,
float right,
float bottom,
float top
)
{
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(left, right, bottom, top, -1, 1);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glDisable(GL_LIGHTING);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texId);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
GLint boundTexture = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTexture);
GLfloat texW, texH;
glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texW);
glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texH);
float u1 = srcRect.x / texW;
float v1 = srcRect.y / texH;
float u2 = (srcRect.x + srcRect.w) / texW;
float v2 = (srcRect.y + srcRect.h) / texH;
float x1 = (float) dstRect.x;
float y1 = (float) dstRect.y;
float x2 = x1 + dstRect.w;
float y2 = y1 + dstRect.h;
glBegin(GL_QUADS);
glTexCoord2f(u1, v1);
glVertex2f(x1, y1);
glTexCoord2f(u2, v1);
glVertex2f(x2, y1);
glTexCoord2f(u2, v2);
glVertex2f(x2, y2);
glTexCoord2f(u1, v2);
glVertex2f(x1, y2);
glEnd();
// Restore state
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
}
void GL11_Download(SDL_Surface* target)
{
glFinish();
glReadPixels(0, 0, target->w, target->h, GL_RGBA, GL_UNSIGNED_BYTE, target->pixels);
}

View File

@ -0,0 +1,88 @@
#pragma once
#include "structs.h"
#include <SDL3/SDL.h>
#include <stdint.h>
#include <vector>
// We don't want to transitively include windows.h, but we need GLuint
typedef unsigned int GLuint;
struct IDirect3DRMTexture;
struct MeshGroup;
typedef float Matrix4x4[4][4];
struct GL11_BridgeVector {
float x, y, z;
};
struct GL11_BridgeTexCoord {
float u, v;
};
struct GL11_BridgeSceneLight {
FColor color;
GL11_BridgeVector position;
float positional;
GL11_BridgeVector direction;
float directional;
};
struct GL11_BridgeSceneVertex {
GL11_BridgeVector position;
GL11_BridgeVector normal;
float tu, tv;
};
struct GLTextureCacheEntry {
IDirect3DRMTexture* texture;
Uint32 version;
GLuint glTextureId;
};
struct GLMeshCacheEntry {
const MeshGroup* meshGroup;
int version;
bool flat;
// non-VBO cache
std::vector<GL11_BridgeVector> positions;
std::vector<GL11_BridgeVector> normals;
std::vector<GL11_BridgeTexCoord> texcoords;
std::vector<uint16_t> indices;
// VBO cache
GLuint vboPositions;
GLuint vboNormals;
GLuint vboTexcoords;
GLuint ibo;
};
void GL11_InitState();
void GL11_LoadExtensions();
void GL11_DestroyTexture(GLuint texId);
GLuint GL11_UploadTextureData(void* pixels, int width, int height);
void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture);
void GL11_DestroyMesh(GLMeshCacheEntry& cache);
void GL11_BeginFrame(const Matrix4x4* projection);
void GL11_UploadLight(int lightIdx, GL11_BridgeSceneLight* l);
void GL11_EnableTransparency();
void GL11_SubmitDraw(
GLMeshCacheEntry& mesh,
const Matrix4x4& modelViewMatrix,
const Appearance& appearance,
GLuint texId
);
void GL11_Resize(int width, int height);
void GL11_Clear(float r, float g, float b);
void GL11_Draw2DImage(
GLuint texId,
const SDL_Rect& srcRect,
const SDL_Rect& dstRect,
float left,
float right,
float bottom,
float top
);
void GL11_Download(SDL_Surface* target);

View File

@ -1,5 +1,4 @@
#include <GL/glew.h>
// must come after GLEW
#include "actual.h"
#include "d3drmrenderer_opengl1.h"
#include "ddraw_impl.h"
#include "ddsurface_impl.h"
@ -11,58 +10,42 @@
#include <cstring>
#include <vector>
static_assert(sizeof(Matrix4x4) == sizeof(D3DRMMATRIX4D), "Matrix4x4 is wrong size");
static_assert(sizeof(GL11_BridgeVector) == sizeof(D3DVECTOR), "GL11_BridgeVector is wrong size");
static_assert(sizeof(GL11_BridgeTexCoord) == sizeof(TexCoord), "GL11_BridgeTexCoord is wrong size");
static_assert(sizeof(GL11_BridgeSceneLight) == sizeof(SceneLight), "GL11_BridgeSceneLight is wrong size");
static_assert(sizeof(GL11_BridgeSceneVertex) == sizeof(D3DRMVERTEX), "GL11_BridgeSceneVertex is wrong size");
Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height)
{
// We have to reset the attributes here after having enumerated the
// OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE
// call below when on an EGL-based backend, and crashes with EGL_BAD_MATCH.
SDL_GL_ResetAttributes();
// But ResetAttributes resets it to 16.
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_Window* window = DDWindow;
bool testWindow = false;
if (!window) {
window = SDL_CreateWindow("OpenGL 1.1 test", width, height, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
if (!window) {
SDL_Log("SDL_CreateWindow: %s", SDL_GetError());
return nullptr;
}
testWindow = true;
if (!DDWindow) {
SDL_Log("No window handler");
return nullptr;
}
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GLContext context = SDL_GL_CreateContext(window);
SDL_GLContext context = SDL_GL_CreateContext(DDWindow);
if (!context) {
SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError());
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
if (!SDL_GL_MakeCurrent(window, context)) {
if (!SDL_GL_MakeCurrent(DDWindow, context)) {
SDL_GL_DestroyContext(context);
SDL_Log("SDL_GL_MakeCurrent: %s", SDL_GetError());
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
GLenum err = glewInit();
if (err != GLEW_OK) {
SDL_Log("glewInit: %s", glewGetErrorString(err));
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CW);
if (testWindow) {
SDL_DestroyWindow(window);
}
GL11_InitState();
return new OpenGL1Renderer(width, height, context);
}
@ -74,12 +57,13 @@ OpenGL1Renderer::OpenGL1Renderer(DWORD width, DWORD height, SDL_GLContext contex
m_virtualWidth = width;
m_virtualHeight = height;
m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32);
m_useVBOs = GLEW_ARB_vertex_buffer_object;
GL11_LoadExtensions();
}
OpenGL1Renderer::~OpenGL1Renderer()
{
SDL_DestroySurface(m_renderedImage);
SDL_GL_DestroyContext(m_context);
}
void OpenGL1Renderer::PushLights(const SceneLight* lightsArray, size_t count)
@ -114,7 +98,7 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t
auto* ctx = static_cast<TextureDestroyContextGL*>(arg);
auto& cache = ctx->renderer->m_textures[ctx->textureId];
if (cache.glTextureId != 0) {
glDeleteTextures(1, &cache.glTextureId);
GL11_DestroyTexture(cache.glTextureId);
cache.glTextureId = 0;
cache.texture = nullptr;
}
@ -124,7 +108,7 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t
);
}
Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture)
Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
@ -133,15 +117,13 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture)
auto& tex = m_textures[i];
if (tex.texture == texture) {
if (tex.version != texture->m_version) {
glDeleteTextures(1, &tex.glTextureId);
glGenTextures(1, &tex.glTextureId);
glBindTexture(GL_TEXTURE_2D, tex.glTextureId);
GL11_DestroyTexture(tex.glTextureId);
SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return NO_TEXTURE_ID;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
tex.glTextureId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h);
SDL_DestroySurface(surf);
tex.version = texture->m_version;
@ -151,14 +133,12 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture)
}
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return NO_TEXTURE_ID;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
texId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h);
SDL_DestroySurface(surf);
for (Uint32 i = 0; i < m_textures.size(); ++i) {
@ -206,52 +186,19 @@ GLMeshCacheEntry GLUploadMesh(const MeshGroup& meshGroup, bool useVBOs)
if (meshGroup.texture) {
cache.texcoords.resize(vertices.size());
std::transform(vertices.begin(), vertices.end(), cache.texcoords.begin(), [](const D3DRMVERTEX& v) {
return v.texCoord;
return GL11_BridgeTexCoord{v.texCoord.u, v.texCoord.v};
});
}
cache.positions.resize(vertices.size());
std::transform(vertices.begin(), vertices.end(), cache.positions.begin(), [](const D3DRMVERTEX& v) {
return v.position;
return GL11_BridgeVector{v.position.x, v.position.y, v.position.z};
});
cache.normals.resize(vertices.size());
std::transform(vertices.begin(), vertices.end(), cache.normals.begin(), [](const D3DRMVERTEX& v) {
return v.normal;
return GL11_BridgeVector{v.normal.x, v.normal.y, v.normal.z};
});
if (useVBOs) {
glGenBuffers(1, &cache.vboPositions);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions);
glBufferData(
GL_ARRAY_BUFFER,
cache.positions.size() * sizeof(D3DVECTOR),
cache.positions.data(),
GL_STATIC_DRAW
);
glGenBuffers(1, &cache.vboNormals);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals);
glBufferData(GL_ARRAY_BUFFER, cache.normals.size() * sizeof(D3DVECTOR), cache.normals.data(), GL_STATIC_DRAW);
if (meshGroup.texture) {
glGenBuffers(1, &cache.vboTexcoords);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords);
glBufferData(
GL_ARRAY_BUFFER,
cache.texcoords.size() * sizeof(TexCoord),
cache.texcoords.data(),
GL_STATIC_DRAW
);
}
glGenBuffers(1, &cache.ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache.ibo);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
cache.indices.size() * sizeof(cache.indices[0]),
cache.indices.data(),
GL_STATIC_DRAW
);
}
GL11_UploadMesh(cache, meshGroup.texture != nullptr);
return cache;
}
@ -269,12 +216,7 @@ void OpenGL1Renderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh)
auto* ctx = static_cast<GLMeshDestroyContext*>(arg);
auto& cache = ctx->renderer->m_meshs[ctx->id];
cache.meshGroup = nullptr;
if (ctx->renderer->m_useVBOs) {
glDeleteBuffers(1, &cache.vboPositions);
glDeleteBuffers(1, &cache.vboNormals);
glDeleteBuffers(1, &cache.vboTexcoords);
glDeleteBuffers(1, &cache.ibo);
}
GL11_DestroyMesh(cache);
delete ctx;
},
ctx
@ -309,110 +251,25 @@ Uint32 OpenGL1Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGr
return (Uint32) (m_meshs.size() - 1);
}
void OpenGL1Renderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc)
{
halDesc->dcmColorModel = D3DCOLORMODEL::RGB;
halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc->dwDeviceZBufferBitDepth = DDBD_24;
helDesc->dwDeviceRenderBitDepth = DDBD_32;
halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
memset(helDesc, 0, sizeof(D3DDEVICEDESC));
}
const char* OpenGL1Renderer::GetName()
{
return "OpenGL 1.1 HAL";
}
HRESULT OpenGL1Renderer::BeginFrame()
{
m_dirty = true;
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glEnable(GL_LIGHTING);
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
// Disable all lights and reset global ambient
for (int i = 0; i < 8; ++i) {
glDisable(GL_LIGHT0 + i);
}
const GLfloat zeroAmbient[4] = {0.f, 0.f, 0.f, 1.f};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, zeroAmbient);
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
// Setup lights
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
GL11_BeginFrame((Matrix4x4*) &m_projection[0][0]);
int lightIdx = 0;
for (const auto& l : m_lights) {
if (lightIdx > 7) {
break;
}
GLenum lightId = GL_LIGHT0 + lightIdx++;
const FColor& c = l.color;
GLfloat col[4] = {c.r, c.g, c.b, c.a};
GL11_UploadLight(lightIdx, (GL11_BridgeSceneLight*) &l);
if (l.positional == 0.f && l.directional == 0.f) {
// Ambient light only
glLightfv(lightId, GL_AMBIENT, col);
const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f};
glLightfv(lightId, GL_DIFFUSE, black);
glLightfv(lightId, GL_SPECULAR, black);
const GLfloat dummyPos[4] = {0.f, 0.f, 1.f, 0.f};
glLightfv(lightId, GL_POSITION, dummyPos);
}
else {
glLightfv(lightId, GL_AMBIENT, zeroAmbient);
glLightfv(lightId, GL_DIFFUSE, col);
if (l.directional == 1.0f) {
glLightfv(lightId, GL_SPECULAR, col);
}
else {
const GLfloat black[4] = {0.f, 0.f, 0.f, 1.f};
glLightfv(lightId, GL_SPECULAR, black);
}
GLfloat pos[4];
if (l.directional == 1.f) {
pos[0] = -l.direction.x;
pos[1] = -l.direction.y;
pos[2] = -l.direction.z;
pos[3] = 0.f;
}
else {
pos[0] = l.position.x;
pos[1] = l.position.y;
pos[2] = l.position.z;
pos[3] = 1.f;
}
glLightfv(lightId, GL_POSITION, pos);
}
glEnable(lightId);
lightIdx++;
}
glPopMatrix();
// Projection and view
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&m_projection[0][0]);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
return DD_OK;
}
void OpenGL1Renderer::EnableTransparency()
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
GL11_EnableTransparency();
}
void OpenGL1Renderer::SubmitDraw(
@ -426,75 +283,14 @@ void OpenGL1Renderer::SubmitDraw(
{
auto& mesh = m_meshs[meshId];
glLoadMatrixf(&modelViewMatrix[0][0]);
glEnable(GL_NORMALIZE);
glColor4ub(appearance.color.r, appearance.color.g, appearance.color.b, appearance.color.a);
if (appearance.shininess != 0.0f) {
GLfloat whiteSpec[] = {1.f, 1.f, 1.f, 1.f};
glMaterialfv(GL_FRONT, GL_SPECULAR, whiteSpec);
glMaterialf(GL_FRONT, GL_SHININESS, appearance.shininess);
}
else {
GLfloat noSpec[] = {0.0f, 0.0f, 0.0f, 0.0f};
glMaterialfv(GL_FRONT, GL_SPECULAR, noSpec);
glMaterialf(GL_FRONT, GL_SHININESS, 0.0f);
}
if (mesh.flat) {
glShadeModel(GL_FLAT);
}
else {
glShadeModel(GL_SMOOTH);
}
// Bind texture if present
if (appearance.textureId != NO_TEXTURE_ID) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
auto& tex = m_textures[appearance.textureId];
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tex.glTextureId);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
GL11_SubmitDraw(mesh, modelViewMatrix, appearance, tex.glTextureId);
}
else {
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
GL11_SubmitDraw(mesh, modelViewMatrix, appearance, 0);
}
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
if (m_useVBOs) {
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions);
glVertexPointer(3, GL_FLOAT, 0, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboNormals);
glNormalPointer(GL_FLOAT, 0, nullptr);
if (appearance.textureId != NO_TEXTURE_ID) {
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboTexcoords);
glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
else {
glVertexPointer(3, GL_FLOAT, 0, mesh.positions.data());
glNormalPointer(GL_FLOAT, 0, mesh.normals.data());
if (appearance.textureId != NO_TEXTURE_ID) {
glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords.data());
}
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()), GL_UNSIGNED_SHORT, mesh.indices.data());
}
glPopMatrix();
}
HRESULT OpenGL1Renderer::FinalizeFrame()
@ -509,16 +305,13 @@ void OpenGL1Renderer::Resize(int width, int height, const ViewportTransform& vie
m_viewportTransform = viewportTransform;
SDL_DestroySurface(m_renderedImage);
m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32);
glViewport(0, 0, m_width, m_height);
GL11_Resize(width, height);
}
void OpenGL1Renderer::Clear(float r, float g, float b)
{
m_dirty = true;
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glClearColor(r, g, b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GL11_Clear(r, g, b);
}
void OpenGL1Renderer::Flip()
@ -532,73 +325,18 @@ void OpenGL1Renderer::Flip()
void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect)
{
m_dirty = true;
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
float left = -m_viewportTransform.offsetX / m_viewportTransform.scale;
float right = (m_width - m_viewportTransform.offsetX) / m_viewportTransform.scale;
float top = -m_viewportTransform.offsetY / m_viewportTransform.scale;
float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale;
glOrtho(left, right, bottom, top, -1, 1);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glDisable(GL_LIGHTING);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, m_textures[textureId].glTextureId);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
GLint boundTexture = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &boundTexture);
GLfloat texW, texH;
glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &texW);
glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &texH);
float u1 = srcRect.x / texW;
float v1 = srcRect.y / texH;
float u2 = (srcRect.x + srcRect.w) / texW;
float v2 = (srcRect.y + srcRect.h) / texH;
float x1 = (float) dstRect.x;
float y1 = (float) dstRect.y;
float x2 = x1 + dstRect.w;
float y2 = y1 + dstRect.h;
glBegin(GL_QUADS);
glTexCoord2f(u1, v1);
glVertex2f(x1, y1);
glTexCoord2f(u2, v1);
glVertex2f(x2, y1);
glTexCoord2f(u2, v2);
glVertex2f(x2, y2);
glTexCoord2f(u1, v2);
glVertex2f(x1, y2);
glEnd();
// Restore state
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
GL11_Draw2DImage(m_textures[textureId].glTextureId, srcRect, dstRect, left, right, bottom, top);
}
void OpenGL1Renderer::Download(SDL_Surface* target)
{
glFinish();
glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_renderedImage->pixels);
GL11_Download(m_renderedImage);
SDL_Rect srcRect = {
static_cast<int>(m_viewportTransform.offsetX),

View File

@ -30,31 +30,28 @@ struct SceneLightGLES2 {
Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height)
{
// We have to reset the attributes here after having enumerated the
// OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE
// call below when on an EGL-based backend, and crashes with EGL_BAD_MATCH.
SDL_GL_ResetAttributes();
// But ResetAttributes resets it to 16.
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_Window* window = DDWindow;
bool testWindow = false;
if (!window) {
window = SDL_CreateWindow("OpenGL ES 2.0 test", width, height, SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
testWindow = true;
}
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GLContext context = SDL_GL_CreateContext(window);
if (!context) {
if (testWindow) {
SDL_DestroyWindow(window);
}
if (!DDWindow) {
SDL_Log("No window handler");
return nullptr;
}
if (!SDL_GL_MakeCurrent(window, context)) {
if (testWindow) {
SDL_DestroyWindow(window);
}
SDL_GLContext context = SDL_GL_CreateContext(DDWindow);
if (!context) {
return nullptr;
}
if (!SDL_GL_MakeCurrent(DDWindow, context)) {
SDL_GL_DestroyContext(context);
return nullptr;
}
@ -170,27 +167,115 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height)
glDeleteShader(vs);
glDeleteShader(fs);
if (testWindow) {
SDL_DestroyWindow(window);
return new OpenGLES2Renderer(width, height, context, shaderProgram);
}
GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = false)
{
GLES2MeshCacheEntry cache{&meshGroup, meshGroup.version};
cache.flat = meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT;
std::vector<D3DRMVERTEX> vertices;
if (cache.flat) {
FlattenSurfaces(
meshGroup.vertices.data(),
meshGroup.vertices.size(),
meshGroup.indices.data(),
meshGroup.indices.size(),
meshGroup.texture != nullptr || forceUV,
vertices,
cache.indices
);
}
else {
vertices = meshGroup.vertices;
cache.indices.resize(meshGroup.indices.size());
std::transform(meshGroup.indices.begin(), meshGroup.indices.end(), cache.indices.begin(), [](DWORD index) {
return static_cast<uint16_t>(index);
});
}
return new OpenGLES2Renderer(width, height, context, shaderProgram);
std::vector<TexCoord> texcoords;
if (meshGroup.texture || forceUV) {
texcoords.resize(vertices.size());
std::transform(vertices.begin(), vertices.end(), texcoords.begin(), [](const D3DRMVERTEX& v) {
return v.texCoord;
});
}
std::vector<D3DVECTOR> positions(vertices.size());
std::transform(vertices.begin(), vertices.end(), positions.begin(), [](const D3DRMVERTEX& v) {
return v.position;
});
std::vector<D3DVECTOR> normals(vertices.size());
std::transform(vertices.begin(), vertices.end(), normals.begin(), [](const D3DRMVERTEX& v) { return v.normal; });
glGenBuffers(1, &cache.vboPositions);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions);
glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(D3DVECTOR), positions.data(), GL_STATIC_DRAW);
glGenBuffers(1, &cache.vboNormals);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals);
glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(D3DVECTOR), normals.data(), GL_STATIC_DRAW);
if (meshGroup.texture || forceUV) {
glGenBuffers(1, &cache.vboTexcoords);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords);
glBufferData(GL_ARRAY_BUFFER, texcoords.size() * sizeof(TexCoord), texcoords.data(), GL_STATIC_DRAW);
}
glGenBuffers(1, &cache.ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache.ibo);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
cache.indices.size() * sizeof(cache.indices[0]),
cache.indices.data(),
GL_STATIC_DRAW
);
return cache;
}
OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext context, GLuint shaderProgram)
: m_context(context), m_shaderProgram(shaderProgram)
{
m_width = width;
m_height = height;
m_virtualWidth = width;
m_virtualHeight = height;
m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32);
ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f};
Resize(width, height, viewportTransform);
m_uiMesh.vertices = {
{{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}},
{{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}},
{{1.0f, 1.0f, 0.0f}, {0, 0, -1}, {1.0f, 1.0f}},
{{0.0f, 1.0f, 0.0f}, {0, 0, -1}, {0.0f, 1.0f}}
};
m_uiMesh.indices = {0, 1, 2, 0, 2, 3};
m_uiMeshCache = GLES2UploadMesh(m_uiMesh, true);
m_posLoc = glGetAttribLocation(m_shaderProgram, "a_position");
m_normLoc = glGetAttribLocation(m_shaderProgram, "a_normal");
m_texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord");
m_colorLoc = glGetUniformLocation(m_shaderProgram, "u_color");
m_shinLoc = glGetUniformLocation(m_shaderProgram, "u_shininess");
m_lightCountLoc = glGetUniformLocation(m_shaderProgram, "u_lightCount");
m_useTextureLoc = glGetUniformLocation(m_shaderProgram, "u_useTexture");
m_textureLoc = glGetUniformLocation(m_shaderProgram, "u_texture");
for (int i = 0; i < 3; ++i) {
std::string base = "u_lights[" + std::to_string(i) + "]";
u_lightLocs[i][0] = glGetUniformLocation(m_shaderProgram, (base + ".color").c_str());
u_lightLocs[i][1] = glGetUniformLocation(m_shaderProgram, (base + ".position").c_str());
u_lightLocs[i][2] = glGetUniformLocation(m_shaderProgram, (base + ".direction").c_str());
}
m_modelViewMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix");
m_normalMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_normalMatrix");
m_projectionMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_projectionMatrix");
}
OpenGLES2Renderer::~OpenGLES2Renderer()
{
SDL_DestroySurface(m_renderedImage);
glDeleteProgram(m_shaderProgram);
SDL_GL_DestroyContext(m_context);
}
void OpenGLES2Renderer::PushLights(const SceneLight* lightsArray, size_t count)
@ -235,7 +320,48 @@ void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture*
);
}
Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture)
bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUi)
{
SDL_Surface* surf = source;
if (source->format != SDL_PIXELFORMAT_RGBA32) {
surf = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return false;
}
}
glGenTextures(1, &outTexId);
glBindTexture(GL_TEXTURE_2D, outTexId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
if (isUi) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
if (strstr((const char*) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) {
GLfloat maxAniso = 0.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso);
GLfloat desiredAniso = fminf(8.0f, maxAniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, desiredAniso);
}
glGenerateMipmap(GL_TEXTURE_2D);
}
if (surf != source) {
SDL_DestroySurface(surf);
}
return true;
}
Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
@ -245,31 +371,18 @@ Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture)
if (tex.texture == texture) {
if (tex.version != texture->m_version) {
glDeleteTextures(1, &tex.glTextureId);
glGenTextures(1, &tex.glTextureId);
glBindTexture(GL_TEXTURE_2D, tex.glTextureId);
SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return NO_TEXTURE_ID;
if (UploadTexture(surface->m_surface, tex.glTextureId, isUi)) {
tex.version = texture->m_version;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
SDL_DestroySurface(surf);
tex.version = texture->m_version;
}
return i;
}
}
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
if (!UploadTexture(surface->m_surface, texId, isUi)) {
return NO_TEXTURE_ID;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
for (Uint32 i = 0; i < m_textures.size(); ++i) {
auto& tex = m_textures[i];
@ -277,85 +390,20 @@ Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture)
tex.texture = texture;
tex.version = texture->m_version;
tex.glTextureId = texId;
tex.width = surf->w;
tex.height = surf->h;
tex.width = surface->m_surface->w;
tex.height = surface->m_surface->h;
AddTextureDestroyCallback(i, texture);
return i;
}
}
m_textures.push_back({texture, texture->m_version, texId, (uint16_t) surf->w, (uint16_t) surf->h});
SDL_DestroySurface(surf);
m_textures.push_back(
{texture, texture->m_version, texId, (uint16_t) surface->m_surface->w, (uint16_t) surface->m_surface->h}
);
AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture);
return (Uint32) (m_textures.size() - 1);
}
GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup)
{
GLES2MeshCacheEntry cache{&meshGroup, meshGroup.version};
cache.flat = meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT;
std::vector<D3DRMVERTEX> vertices;
if (cache.flat) {
FlattenSurfaces(
meshGroup.vertices.data(),
meshGroup.vertices.size(),
meshGroup.indices.data(),
meshGroup.indices.size(),
meshGroup.texture != nullptr,
vertices,
cache.indices
);
}
else {
vertices = meshGroup.vertices;
cache.indices.resize(meshGroup.indices.size());
std::transform(meshGroup.indices.begin(), meshGroup.indices.end(), cache.indices.begin(), [](DWORD index) {
return static_cast<uint16_t>(index);
});
}
std::vector<TexCoord> texcoords;
if (meshGroup.texture) {
texcoords.resize(vertices.size());
std::transform(vertices.begin(), vertices.end(), texcoords.begin(), [](const D3DRMVERTEX& v) {
return v.texCoord;
});
}
std::vector<D3DVECTOR> positions(vertices.size());
std::transform(vertices.begin(), vertices.end(), positions.begin(), [](const D3DRMVERTEX& v) {
return v.position;
});
std::vector<D3DVECTOR> normals(vertices.size());
std::transform(vertices.begin(), vertices.end(), normals.begin(), [](const D3DRMVERTEX& v) { return v.normal; });
glGenBuffers(1, &cache.vboPositions);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions);
glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(D3DVECTOR), positions.data(), GL_STATIC_DRAW);
glGenBuffers(1, &cache.vboNormals);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals);
glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(D3DVECTOR), normals.data(), GL_STATIC_DRAW);
if (meshGroup.texture) {
glGenBuffers(1, &cache.vboTexcoords);
glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords);
glBufferData(GL_ARRAY_BUFFER, texcoords.size() * sizeof(TexCoord), texcoords.data(), GL_STATIC_DRAW);
}
glGenBuffers(1, &cache.ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache.ibo);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
cache.indices.size() * sizeof(cache.indices[0]),
cache.indices.data(),
GL_STATIC_DRAW
);
return cache;
}
struct GLES2MeshDestroyContext {
OpenGLES2Renderer* renderer;
Uint32 id;
@ -407,37 +455,11 @@ Uint32 OpenGLES2Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* mesh
return (Uint32) (m_meshs.size() - 1);
}
void OpenGLES2Renderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc)
{
halDesc->dcmColorModel = D3DCOLORMODEL::RGB;
halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc->dwDeviceZBufferBitDepth = DDBD_16;
const char* extensions = (const char*) glGetString(GL_EXTENSIONS);
if (extensions) {
if (strstr(extensions, "GL_OES_depth24")) {
halDesc->dwDeviceZBufferBitDepth |= DDBD_24;
}
if (strstr(extensions, "GL_OES_depth32")) {
halDesc->dwDeviceZBufferBitDepth |= DDBD_32;
}
}
helDesc->dwDeviceRenderBitDepth = DDBD_32;
halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
memset(helDesc, 0, sizeof(D3DDEVICEDESC));
}
const char* OpenGLES2Renderer::GetName()
{
return "OpenGL ES 2.0 HAL";
}
HRESULT OpenGLES2Renderer::BeginFrame()
{
m_dirty = true;
glEnable(GL_CULL_FACE);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
@ -466,12 +488,11 @@ HRESULT OpenGLES2Renderer::BeginFrame()
}
for (int i = 0; i < lightCount; ++i) {
std::string base = "u_lights[" + std::to_string(i) + "]";
glUniform4fv(glGetUniformLocation(m_shaderProgram, (base + ".color").c_str()), 1, lightData[i].color);
glUniform4fv(glGetUniformLocation(m_shaderProgram, (base + ".position").c_str()), 1, lightData[i].position);
glUniform4fv(glGetUniformLocation(m_shaderProgram, (base + ".direction").c_str()), 1, lightData[i].direction);
glUniform4fv(u_lightLocs[i][0], 1, lightData[i].color);
glUniform4fv(u_lightLocs[i][1], 1, lightData[i].position);
glUniform4fv(u_lightLocs[i][2], 1, lightData[i].direction);
}
glUniform1i(glGetUniformLocation(m_shaderProgram, "u_lightCount"), lightCount);
glUniform1i(m_lightCountLoc, lightCount);
return DD_OK;
}
@ -493,55 +514,49 @@ void OpenGLES2Renderer::SubmitDraw(
{
auto& mesh = m_meshs[meshId];
glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 1, GL_FALSE, &modelViewMatrix[0][0]);
glUniformMatrix3fv(glGetUniformLocation(m_shaderProgram, "u_normalMatrix"), 1, GL_FALSE, &normalMatrix[0][0]);
glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"), 1, GL_FALSE, &m_projection[0][0]);
glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelViewMatrix[0][0]);
glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &normalMatrix[0][0]);
glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &m_projection[0][0]);
glUniform4f(
glGetUniformLocation(m_shaderProgram, "u_color"),
m_colorLoc,
appearance.color.r / 255.0f,
appearance.color.g / 255.0f,
appearance.color.b / 255.0f,
appearance.color.a / 255.0f
);
glUniform1f(glGetUniformLocation(m_shaderProgram, "u_shininess"), appearance.shininess);
glUniform1f(m_shinLoc, appearance.shininess);
if (appearance.textureId != NO_TEXTURE_ID) {
glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 1);
glUniform1i(m_useTextureLoc, 1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_textures[appearance.textureId].glTextureId);
glUniform1i(glGetUniformLocation(m_shaderProgram, "u_texture"), 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glUniform1i(m_textureLoc, 0);
}
else {
glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 0);
glUniform1i(m_useTextureLoc, 0);
}
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions);
GLint posLoc = glGetAttribLocation(m_shaderProgram, "a_position");
glEnableVertexAttribArray(posLoc);
glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(m_posLoc);
glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboNormals);
GLint normLoc = glGetAttribLocation(m_shaderProgram, "a_normal");
glEnableVertexAttribArray(normLoc);
glVertexAttribPointer(normLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(m_normLoc);
glVertexAttribPointer(m_normLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord");
if (appearance.textureId != NO_TEXTURE_ID) {
glBindBuffer(GL_ARRAY_BUFFER, mesh.vboTexcoords);
glEnableVertexAttribArray(texLoc);
glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(m_texLoc);
glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr);
glDisableVertexAttribArray(posLoc);
glDisableVertexAttribArray(normLoc);
glDisableVertexAttribArray(texLoc);
glDisableVertexAttribArray(m_normLoc);
glDisableVertexAttribArray(m_texLoc);
}
HRESULT OpenGLES2Renderer::FinalizeFrame()
@ -558,7 +573,9 @@ void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& v
m_width = width;
m_height = height;
m_viewportTransform = viewportTransform;
SDL_DestroySurface(m_renderedImage);
if (m_renderedImage) {
SDL_DestroySurface(m_renderedImage);
}
m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32);
glViewport(0, 0, m_width, m_height);
}
@ -580,35 +597,6 @@ void OpenGLES2Renderer::Flip()
}
}
void CreateOrthoMatrix(float left, float right, float bottom, float top, D3DRMMATRIX4D& outMatrix)
{
float near = -1.0f;
float far = 1.0f;
float rl = right - left;
float tb = top - bottom;
float fn = far - near;
outMatrix[0][0] = 2.0f / rl;
outMatrix[0][1] = 0.0f;
outMatrix[0][2] = 0.0f;
outMatrix[0][3] = 0.0f;
outMatrix[1][0] = 0.0f;
outMatrix[1][1] = 2.0f / tb;
outMatrix[1][2] = 0.0f;
outMatrix[1][3] = 0.0f;
outMatrix[2][0] = 0.0f;
outMatrix[2][1] = 0.0f;
outMatrix[2][2] = -2.0f / fn;
outMatrix[2][3] = 0.0f;
outMatrix[3][0] = -(right + left) / rl;
outMatrix[3][1] = -(top + bottom) / tb;
outMatrix[3][2] = -(far + near) / fn;
outMatrix[3][3] = 1.0f;
}
void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect)
{
m_dirty = true;
@ -620,68 +608,70 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c
float color[] = {1.0f, 1.0f, 1.0f, 1.0f};
float blank[] = {0.0f, 0.0f, 0.0f, 0.0f};
glUniform4fv(glGetUniformLocation(m_shaderProgram, "u_lights[0].color"), 1, color);
glUniform4fv(glGetUniformLocation(m_shaderProgram, "u_lights[0].position"), 1, blank);
glUniform4fv(glGetUniformLocation(m_shaderProgram, "u_lights[0].direction"), 1, blank);
glUniform1i(glGetUniformLocation(m_shaderProgram, "u_lightCount"), 1);
glUniform4fv(u_lightLocs[0][0], 1, color);
glUniform4fv(u_lightLocs[0][1], 1, blank);
glUniform4fv(u_lightLocs[0][2], 1, blank);
glUniform1i(m_lightCountLoc, 1);
glUniform4f(glGetUniformLocation(m_shaderProgram, "u_color"), 1.0f, 1.0f, 1.0f, 1.0f);
glUniform1f(glGetUniformLocation(m_shaderProgram, "u_shininess"), 0.0f);
glUniform4f(m_colorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
glUniform1f(m_shinLoc, 0.0f);
float left = -m_viewportTransform.offsetX / m_viewportTransform.scale;
float right = (m_width - m_viewportTransform.offsetX) / m_viewportTransform.scale;
float top = -m_viewportTransform.offsetY / m_viewportTransform.scale;
float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale;
const GLES2TextureCacheEntry& texture = m_textures[textureId];
float scaleX = static_cast<float>(dstRect.w) / srcRect.w;
float scaleY = static_cast<float>(dstRect.h) / srcRect.h;
SDL_Rect expandedDstRect = {
static_cast<int>(std::round(dstRect.x - srcRect.x * scaleX)),
static_cast<int>(std::round(dstRect.y - srcRect.y * scaleY)),
static_cast<int>(std::round(texture.width * scaleX)),
static_cast<int>(std::round(texture.height * scaleY))
};
D3DRMMATRIX4D projection;
CreateOrthoMatrix(left, right, bottom, top, projection);
D3DRMMATRIX4D modelView, projection;
Create2DTransformMatrix(
expandedDstRect,
m_viewportTransform.scale,
m_viewportTransform.offsetX,
m_viewportTransform.offsetY,
modelView
);
D3DRMMATRIX4D identity = {{1.f, 0.f, 0.f, 0.f}, {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}};
glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 1, GL_FALSE, &identity[0][0]);
glUniformMatrix3fv(glGetUniformLocation(m_shaderProgram, "u_normalMatrix"), 1, GL_FALSE, &identity[0][0]);
glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"), 1, GL_FALSE, &projection[0][0]);
glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelView[0][0]);
Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}};
glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]);
CreateOrthographicProjection((float) m_width, (float) m_height, projection);
glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &projection[0][0]);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glActiveTexture(GL_TEXTURE0);
glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 1);
const GLES2TextureCacheEntry& texture = m_textures[textureId];
glUniform1i(m_useTextureLoc, 1);
glBindTexture(GL_TEXTURE_2D, texture.glTextureId);
glUniform1i(glGetUniformLocation(m_shaderProgram, "u_texture"), 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
float texW = texture.width;
float texH = texture.height;
glUniform1i(m_textureLoc, 0);
float u1 = srcRect.x / texW;
float v1 = srcRect.y / texH;
float u2 = (srcRect.x + srcRect.w) / texW;
float v2 = (srcRect.y + srcRect.h) / texH;
glEnable(GL_SCISSOR_TEST);
glScissor(
static_cast<int>(std::round(dstRect.x * m_viewportTransform.scale + m_viewportTransform.offsetX)),
m_height - static_cast<int>(
std::round((dstRect.y + dstRect.h) * m_viewportTransform.scale + m_viewportTransform.offsetY)
),
static_cast<int>(std::round(dstRect.w * m_viewportTransform.scale)),
static_cast<int>(std::round(dstRect.h * m_viewportTransform.scale))
);
float x1 = static_cast<float>(dstRect.x);
float y1 = static_cast<float>(dstRect.y);
float x2 = x1 + dstRect.w;
float y2 = y1 + dstRect.h;
glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions);
glEnableVertexAttribArray(m_posLoc);
glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
GLfloat vertices[] = {x1, y1, u1, v1, x2, y1, u2, v1, x1, y2, u1, v2, x2, y2, u2, v2};
glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords);
glEnableVertexAttribArray(m_texLoc);
glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
GLint posLoc = glGetAttribLocation(m_shaderProgram, "a_position");
GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord");
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr);
glEnableVertexAttribArray(posLoc);
glEnableVertexAttribArray(texLoc);
glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), vertices);
glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), vertices + 2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(posLoc);
glDisableVertexAttribArray(texLoc);
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0);
glDisableVertexAttribArray(m_texLoc);
glDisable(GL_SCISSOR_TEST);
}
void OpenGLES2Renderer::Download(SDL_Surface* target)

View File

@ -203,49 +203,31 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height)
return nullptr;
}
SDL_Window* window = DDWindow;
bool testWindow = false;
if (!window) {
window = SDL_CreateWindow("SDL_GPU test", width, height, SDL_WINDOW_HIDDEN);
if (!window) {
SDL_Log("SDL_CreateWindow: %s", SDL_GetError());
return nullptr;
}
testWindow = true;
}
if (!SDL_ClaimWindowForGPUDevice(device.ptr, window)) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_ClaimWindowForGPUDevice: %s", SDL_GetError());
if (testWindow) {
SDL_DestroyWindow(window);
}
if (!DDWindow) {
SDL_Log("No window handler");
return nullptr;
}
ScopedPipeline opaquePipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, window, true, true)};
if (!SDL_ClaimWindowForGPUDevice(device.ptr, DDWindow)) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_ClaimWindowForGPUDevice: %s", SDL_GetError());
return nullptr;
}
ScopedPipeline opaquePipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, true, true)};
if (!opaquePipeline.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for opaquePipeline");
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
ScopedPipeline transparentPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, window, true, false)};
ScopedPipeline transparentPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, true, false)};
if (!transparentPipeline.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for transparentPipeline");
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
ScopedPipeline uiPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, window, false, false)};
ScopedPipeline uiPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, false, false)};
if (!uiPipeline.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for uiPipeline");
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
@ -257,9 +239,6 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height)
ScopedTransferBuffer uploadBuffer{device.ptr, SDL_CreateGPUTransferBuffer(device.ptr, &uploadBufferInfo)};
if (!uploadBuffer.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUTransferBuffer filed for upload buffer (%s)", SDL_GetError());
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
@ -273,9 +252,6 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height)
ScopedSampler sampler{device.ptr, SDL_CreateGPUSampler(device.ptr, &samplerInfo)};
if (!sampler.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create sampler: %s", SDL_GetError());
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
@ -289,17 +265,9 @@ Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height)
ScopedSampler uiSampler{device.ptr, SDL_CreateGPUSampler(device.ptr, &uiSamplerInfo)};
if (!uiSampler.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create sampler: %s", SDL_GetError());
if (testWindow) {
SDL_DestroyWindow(window);
}
return nullptr;
}
if (testWindow) {
SDL_ReleaseWindowFromGPUDevice(device.ptr, window);
SDL_DestroyWindow(window);
}
auto renderer = new Direct3DRMSDL3GPURenderer(
width,
height,
@ -383,9 +351,7 @@ Direct3DRMSDL3GPURenderer::~Direct3DRMSDL3GPURenderer()
{
SDL_ReleaseGPUBuffer(m_device, m_uiMeshCache.vertexBuffer);
SDL_ReleaseGPUBuffer(m_device, m_uiMeshCache.indexBuffer);
if (DDWindow) {
SDL_ReleaseWindowFromGPUDevice(m_device, DDWindow);
}
SDL_ReleaseWindowFromGPUDevice(m_device, DDWindow);
if (m_downloadBuffer) {
SDL_ReleaseGPUTransferBuffer(m_device, m_downloadBuffer);
}
@ -533,7 +499,7 @@ SDL_GPUTexture* Direct3DRMSDL3GPURenderer::CreateTextureFromSurface(SDL_Surface*
return texptr;
}
Uint32 Direct3DRMSDL3GPURenderer::GetTextureId(IDirect3DRMTexture* iTexture)
Uint32 Direct3DRMSDL3GPURenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
@ -585,7 +551,7 @@ SDL3MeshCache Direct3DRMSDL3GPURenderer::UploadMesh(const MeshGroup& meshGroup)
meshGroup.vertices.size(),
meshGroup.indices.data(),
meshGroup.indices.size(),
meshGroup.texture != nullptr,
true,
finalVertices,
newIndices
);
@ -712,24 +678,6 @@ Uint32 Direct3DRMSDL3GPURenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGro
return (Uint32) (m_meshs.size() - 1);
}
void Direct3DRMSDL3GPURenderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc)
{
halDesc->dcmColorModel = D3DCOLORMODEL::RGB;
halDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc->dwDeviceZBufferBitDepth = DDBD_32; // Todo add support for other depths
halDesc->dwDeviceRenderBitDepth = DDBD_32;
halDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
memset(helDesc, 0, sizeof(D3DDEVICEDESC));
}
const char* Direct3DRMSDL3GPURenderer::GetName()
{
return "SDL3 GPU HAL";
}
void PackNormalMatrix(const Matrix3x3& normalMatrix3x3, D3DRMMATRIX4D& packedNormalMatrix4x4)
{
for (int row = 0; row < 3; ++row) {
@ -857,10 +805,6 @@ void Direct3DRMSDL3GPURenderer::Resize(int width, int height, const ViewportTran
m_height = height;
m_viewportTransform = viewportTransform;
if (!DDWindow) {
return;
}
if (m_transferTexture) {
SDL_ReleaseGPUTexture(m_device, m_transferTexture);
}
@ -940,37 +884,6 @@ void Direct3DRMSDL3GPURenderer::Flip()
m_cmdbuf = nullptr;
}
// TODO use SDL_SetGPUScissor(SDL_GPURenderPass *render_pass, const SDL_Rect *scissor) when srcRect isn't 100% of
// texture
void Create2DTransformMatrix(
const SDL_Rect& dstRect,
float scale,
float offsetX,
float offsetY,
D3DRMMATRIX4D& outMatrix
)
{
float x = static_cast<float>(dstRect.x) * scale + offsetX;
float y = static_cast<float>(dstRect.y) * scale + offsetY;
float w = static_cast<float>(dstRect.w) * scale;
float h = static_cast<float>(dstRect.h) * scale;
D3DVALUE tmp[4][4] = {{w, 0, 0, 0}, {0, h, 0, 0}, {0, 0, 1, 0}, {x, y, 0, 1}};
memcpy(outMatrix, tmp, sizeof(tmp));
}
void CreateOrthographicProjection(float width, float height, D3DRMMATRIX4D& outProj)
{
D3DVALUE tmp[4][4] = {
{2.0f / width, 0.0f, 0.0f, 0.0f},
{0.0f, -2.0f / height, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
{-1.0f, 1.0f, 0.0f, 1.0f}
};
memcpy(outProj, tmp, sizeof(tmp));
}
void Direct3DRMSDL3GPURenderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect)
{
if (!m_renderPass) {

View File

@ -80,7 +80,7 @@ void Direct3DRMSoftwareRenderer::ClearZBuffer()
_mm_empty();
}
#endif
#elif defined(__arm__) || defined(__aarch64__)
#elif (defined(__arm__) || defined(__aarch64__)) && !defined(__3DS__)
if (SDL_HasNEON()) {
float32x4_t inf4 = vdupq_n_f32(inf);
for (; i + 4 <= size; i += 4) {
@ -554,7 +554,7 @@ void Direct3DRMSoftwareRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DR
);
}
Uint32 Direct3DRMSoftwareRenderer::GetTextureId(IDirect3DRMTexture* iTexture)
Uint32 Direct3DRMSoftwareRenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
@ -664,24 +664,6 @@ Uint32 Direct3DRMSoftwareRenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGr
return (Uint32) (m_meshs.size() - 1);
}
void Direct3DRMSoftwareRenderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc)
{
memset(halDesc, 0, sizeof(D3DDEVICEDESC));
helDesc->dcmColorModel = D3DCOLORMODEL::RGB;
helDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
helDesc->dwDeviceZBufferBitDepth = DDBD_32;
helDesc->dwDeviceRenderBitDepth = DDBD_32;
helDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
helDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
helDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
}
const char* Direct3DRMSoftwareRenderer::GetName()
{
return "Miniwin Emulation";
}
HRESULT Direct3DRMSoftwareRenderer::BeginFrame()
{
if (!m_renderedImage || !SDL_LockSurface(m_renderedImage)) {

View File

@ -13,6 +13,9 @@
#ifdef USE_OPENGLES2
#include "d3drmrenderer_opengles2.h"
#endif
#ifdef __3DS__
#include "d3drmrenderer_citro3d.h"
#endif
#ifdef _WIN32
#include "d3drmrenderer_directx9.h"
#endif
@ -159,6 +162,11 @@ HRESULT Direct3DRMImpl::CreateDeviceFromSurface(
DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight);
}
#endif
#ifdef __3DS__
else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) {
DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight);
}
#endif
#ifdef _WIN32
else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) {
DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight);

View File

@ -155,6 +155,10 @@ void Direct3DRMDevice2Impl::Resize()
{
int width, height;
SDL_GetWindowSizeInPixels(DDWindow, &width, &height);
#ifdef __3DS__
width = 320; // We are on the lower screen
height = 240;
#endif
m_viewportTransform = CalculateViewportTransform(m_virtualWidth, m_virtualHeight, width, height);
m_renderer->Resize(width, height, m_viewportTransform);
for (int i = 0; i < m_viewports->GetSize(); i++) {

View File

@ -4,6 +4,9 @@
#ifdef USE_OPENGLES2
#include "d3drmrenderer_opengles2.h"
#endif
#ifdef __3DS__
#include "d3drmrenderer_citro3d.h"
#endif
#ifdef _WIN32
#include "d3drmrenderer_directx9.h"
#endif
@ -207,14 +210,18 @@ HRESULT DirectDrawImpl::GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps)
return S_OK;
}
void EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx, Direct3DRMRenderer* device, GUID deviceGuid)
void EnumDevice(
LPD3DENUMDEVICESCALLBACK cb,
void* ctx,
const char* name,
D3DDEVICEDESC* halDesc,
D3DDEVICEDESC* helDesc,
GUID deviceGuid
)
{
D3DDEVICEDESC halDesc = {};
D3DDEVICEDESC helDesc = {};
device->GetDesc(&halDesc, &helDesc);
char* deviceNameDup = SDL_strdup(device->GetName());
char* deviceNameDup = SDL_strdup(name);
char* deviceDescDup = SDL_strdup("Miniwin driver");
cb(&deviceGuid, deviceNameDup, deviceDescDup, &halDesc, &helDesc, ctx);
cb(&deviceGuid, deviceNameDup, deviceDescDup, halDesc, helDesc, ctx);
SDL_free(deviceDescDup);
SDL_free(deviceNameDup);
}
@ -228,6 +235,9 @@ HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
#ifdef USE_OPENGL1
OpenGL1Renderer_EnumDevice(cb, ctx);
#endif
#ifdef __3DS__
Citro3DRenderer_EnumDevice(cb, ctx);
#endif
#ifdef _WIN32
DirectX9Renderer_EnumDevice(cb, ctx);
#endif
@ -342,6 +352,11 @@ HRESULT DirectDrawImpl::CreateDevice(
DDRenderer = OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight);
}
#endif
#ifdef __3DS__
else if (SDL_memcmp(&guid, &Citro3D_GUID, sizeof(GUID)) == 0) {
DDRenderer = new Citro3DRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight);
}
#endif
#ifdef _WIN32
else if (SDL_memcmp(&guid, &DirectX9_GUID, sizeof(GUID)) == 0) {
DDRenderer = DirectX9Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight);

View File

@ -62,7 +62,7 @@ HRESULT FrameBufferImpl::Blt(
if (!surface) {
return DDERR_GENERIC;
}
Uint32 textureId = DDRenderer->GetTextureId(surface->ToTexture());
Uint32 textureId = DDRenderer->GetTextureId(surface->ToTexture(), true);
SDL_Rect srcRect =
lpSrcRect ? ConvertRect(lpSrcRect) : SDL_Rect{0, 0, surface->m_surface->w, surface->m_surface->h};
SDL_Rect dstRect =

View File

@ -31,14 +31,12 @@ class Direct3DRMRenderer : public IDirect3DDevice2 {
virtual void PushLights(const SceneLight* vertices, size_t count) = 0;
virtual void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) = 0;
virtual void SetFrustumPlanes(const Plane* frustumPlanes) = 0;
virtual Uint32 GetTextureId(IDirect3DRMTexture* texture) = 0;
virtual Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi = false) = 0;
virtual Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) = 0;
int GetWidth() { return m_width; }
int GetHeight() { return m_height; }
int GetVirtualWidth() { return m_virtualWidth; }
int GetVirtualHeight() { return m_virtualHeight; }
virtual void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) = 0;
virtual const char* GetName() = 0;
virtual HRESULT BeginFrame() = 0;
virtual void EnableTransparency() = 0;
virtual void SubmitDraw(

View File

@ -0,0 +1,82 @@
#pragma once
#include "d3drmrenderer.h"
#include "ddraw_impl.h"
#include <SDL3/SDL.h>
#include <citro3d.h>
#include <vector>
DEFINE_GUID(Citro3D_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x53);
struct C3DTextureCacheEntry {
IDirect3DRMTexture* texture;
Uint32 version;
C3D_Tex c3dTex;
uint16_t width;
uint16_t height;
};
struct C3DMeshCacheEntry {
const MeshGroup* meshGroup = nullptr;
int version = 0;
void* vbo = nullptr;
int vertexCount = 0;
};
class Citro3DRenderer : public Direct3DRMRenderer {
public:
Citro3DRenderer(DWORD width, DWORD height);
~Citro3DRenderer() override;
void PushLights(const SceneLight* lightsArray, size_t count) override;
void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override;
void SetFrustumPlanes(const Plane* frustumPlanes) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override;
Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) 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) override;
void Download(SDL_Surface* target) override;
private:
void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture);
void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh);
void StartFrame();
D3DRMMATRIX4D m_projection;
SDL_Surface* m_renderedImage;
C3D_RenderTarget* m_renderTarget;
std::vector<C3DTextureCacheEntry> m_textures;
std::vector<C3DMeshCacheEntry> m_meshs;
ViewportTransform m_viewportTransform;
std::vector<SceneLight> m_lights;
};
inline static void Citro3DRenderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
{
D3DDEVICEDESC halDesc = {};
halDesc.dcmColorModel = D3DCOLOR_RGB;
halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc.dwDeviceZBufferBitDepth = DDBD_24;
halDesc.dwDeviceRenderBitDepth = DDBD_32;
halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
D3DDEVICEDESC helDesc = {};
EnumDevice(cb, ctx, "Citro3D", &halDesc, &helDesc, Citro3D_GUID);
}

View File

@ -18,10 +18,8 @@ class DirectX9Renderer : public Direct3DRMRenderer {
void PushLights(const SceneLight* lightsArray, size_t count) override;
void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override;
void SetFrustumPlanes(const Plane* frustumPlanes) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override;
Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override;
void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override;
const char* GetName() override;
HRESULT BeginFrame() override;
void EnableTransparency() override;
void SubmitDraw(
@ -52,8 +50,21 @@ class DirectX9Renderer : public Direct3DRMRenderer {
inline static void DirectX9Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
{
Direct3DRMRenderer* device = DirectX9Renderer::Create(640, 480);
if (device) {
EnumDevice(cb, ctx, device, DirectX9_GUID);
delete device;
if (!device) {
return;
}
delete device;
D3DDEVICEDESC halDesc = {};
halDesc.dcmColorModel = D3DCOLOR_RGB;
halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc.dwDeviceZBufferBitDepth = DDBD_24;
halDesc.dwDeviceRenderBitDepth = DDBD_32;
halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
D3DDEVICEDESC helDesc = {};
EnumDevice(cb, ctx, "DirectX 9 HAL", &halDesc, &helDesc, DirectX9_GUID);
}

View File

@ -1,44 +1,14 @@
#pragma once
#include "../d3drm/backends/opengl1/actual.h"
#include "d3drmrenderer.h"
#include "d3drmtexture_impl.h"
#include "ddraw_impl.h"
#ifdef __APPLE__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#include <SDL3/SDL.h>
#include <vector>
DEFINE_GUID(OpenGL1_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03);
struct GLTextureCacheEntry {
IDirect3DRMTexture* texture;
Uint32 version;
GLuint glTextureId;
};
struct GLMeshCacheEntry {
const MeshGroup* meshGroup;
int version;
bool flat;
// non-VBO cache
std::vector<D3DVECTOR> positions;
std::vector<D3DVECTOR> normals;
std::vector<TexCoord> texcoords;
std::vector<uint16_t> indices;
// VBO cache
GLuint vboPositions;
GLuint vboNormals;
GLuint vboTexcoords;
GLuint ibo;
};
class OpenGL1Renderer : public Direct3DRMRenderer {
public:
static Direct3DRMRenderer* Create(DWORD width, DWORD height);
@ -48,10 +18,8 @@ class OpenGL1Renderer : public Direct3DRMRenderer {
void PushLights(const SceneLight* lightsArray, size_t count) override;
void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override;
void SetFrustumPlanes(const Plane* frustumPlanes) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override;
Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override;
void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override;
const char* GetName() override;
HRESULT BeginFrame() override;
void EnableTransparency() override;
void SubmitDraw(
@ -87,8 +55,21 @@ class OpenGL1Renderer : public Direct3DRMRenderer {
inline static void OpenGL1Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
{
Direct3DRMRenderer* device = OpenGL1Renderer::Create(640, 480);
if (device) {
EnumDevice(cb, ctx, device, OpenGL1_GUID);
delete device;
if (!device) {
return;
}
delete device;
D3DDEVICEDESC halDesc = {};
halDesc.dcmColorModel = D3DCOLORMODEL::RGB;
halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc.dwDeviceZBufferBitDepth = DDBD_24;
halDesc.dwDeviceRenderBitDepth = DDBD_32;
halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
D3DDEVICEDESC helDesc = {};
EnumDevice(cb, ctx, "OpenGL 1.1 HAL", &halDesc, &helDesc, OpenGL1_GUID);
}

View File

@ -39,10 +39,8 @@ class OpenGLES2Renderer : public Direct3DRMRenderer {
void PushLights(const SceneLight* lightsArray, size_t count) override;
void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override;
void SetFrustumPlanes(const Plane* frustumPlanes) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) override;
Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override;
void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override;
const char* GetName() override;
HRESULT BeginFrame() override;
void EnableTransparency() override;
void SubmitDraw(
@ -64,22 +62,60 @@ class OpenGLES2Renderer : public Direct3DRMRenderer {
void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture);
void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh);
MeshGroup m_uiMesh;
GLES2MeshCacheEntry m_uiMeshCache;
std::vector<GLES2TextureCacheEntry> m_textures;
std::vector<GLES2MeshCacheEntry> m_meshs;
D3DRMMATRIX4D m_projection;
SDL_Surface* m_renderedImage;
SDL_Surface* m_renderedImage = nullptr;
bool m_dirty = false;
std::vector<SceneLight> m_lights;
SDL_GLContext m_context;
GLuint m_shaderProgram;
GLint m_posLoc;
GLint m_normLoc;
GLint m_texLoc;
GLint m_colorLoc;
GLint m_shinLoc;
GLint m_lightCountLoc;
GLint m_useTextureLoc;
GLint m_textureLoc;
GLint u_lightLocs[3][3];
GLint m_modelViewMatrixLoc;
GLint m_normalMatrixLoc;
GLint m_projectionMatrixLoc;
ViewportTransform m_viewportTransform;
};
inline static void OpenGLES2Renderer_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
{
Direct3DRMRenderer* device = OpenGLES2Renderer::Create(640, 480);
if (device) {
EnumDevice(cb, ctx, device, OpenGLES2_GUID);
delete device;
if (!device) {
return;
}
D3DDEVICEDESC halDesc = {};
halDesc.dcmColorModel = D3DCOLOR_RGB;
halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc.dwDeviceZBufferBitDepth = DDBD_16;
halDesc.dwDeviceRenderBitDepth = DDBD_32;
halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
const char* extensions = (const char*) glGetString(GL_EXTENSIONS);
if (extensions) {
if (strstr(extensions, "GL_OES_depth24")) {
halDesc.dwDeviceZBufferBitDepth |= DDBD_24;
}
if (strstr(extensions, "GL_OES_depth32")) {
halDesc.dwDeviceZBufferBitDepth |= DDBD_32;
}
}
delete device;
D3DDEVICEDESC helDesc = {};
EnumDevice(cb, ctx, "OpenGL ES 2.0 HAL", &halDesc, &helDesc, OpenGLES2_GUID);
}

View File

@ -47,12 +47,10 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer {
static Direct3DRMRenderer* Create(DWORD width, DWORD height);
~Direct3DRMSDL3GPURenderer() override;
void PushLights(const SceneLight* vertices, size_t count) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) 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;
void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override;
const char* GetName() override;
HRESULT BeginFrame() override;
void EnableTransparency() override;
void SubmitDraw(
@ -122,8 +120,21 @@ class Direct3DRMSDL3GPURenderer : public Direct3DRMRenderer {
inline static void Direct3DRMSDL3GPU_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
{
Direct3DRMRenderer* device = Direct3DRMSDL3GPURenderer::Create(640, 480);
if (device) {
EnumDevice(cb, ctx, device, SDL3_GPU_GUID);
delete device;
if (!device) {
return;
}
delete device;
D3DDEVICEDESC halDesc = {};
halDesc.dcmColorModel = D3DCOLOR_RGB;
halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
halDesc.dwDeviceZBufferBitDepth = DDBD_32;
halDesc.dwDeviceRenderBitDepth = DDBD_32;
halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
D3DDEVICEDESC helDesc = {};
EnumDevice(cb, ctx, "SDL3 GPU HAL", &halDesc, &helDesc, SDL3_GPU_GUID);
}

View File

@ -29,12 +29,10 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer {
Direct3DRMSoftwareRenderer(DWORD width, DWORD height);
~Direct3DRMSoftwareRenderer() override;
void PushLights(const SceneLight* vertices, size_t count) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture) override;
Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUi) 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;
void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override;
const char* GetName() override;
HRESULT BeginFrame() override;
void EnableTransparency() override;
void SubmitDraw(
@ -87,8 +85,16 @@ class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer {
inline static void Direct3DRMSoftware_EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx)
{
Direct3DRMRenderer* device = nullptr;
device = new Direct3DRMSoftwareRenderer(640, 480);
EnumDevice(cb, ctx, device, SOFTWARE_GUID);
delete device;
D3DDEVICEDESC halDesc = {};
D3DDEVICEDESC helDesc = {};
helDesc.dcmColorModel = D3DCOLOR_RGB;
helDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH;
helDesc.dwDeviceZBufferBitDepth = DDBD_32;
helDesc.dwDeviceRenderBitDepth = DDBD_32;
helDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE;
helDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND;
helDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR;
EnumDevice(cb, ctx, "Miniwin Emulation", &halDesc, &helDesc, SOFTWARE_GUID);
}

View File

@ -56,4 +56,11 @@ HRESULT DirectDrawEnumerate(LPDDENUMCALLBACKA cb, void* context);
HRESULT DirectDrawCreate(LPGUID lpGuid, LPDIRECTDRAW* lplpDD, IUnknown* pUnkOuter);
void EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx, Direct3DRMRenderer* device, GUID deviceGuid);
void EnumDevice(
LPD3DENUMDEVICESCALLBACK cb,
void* ctx,
const char* name,
D3DDEVICEDESC* halDesc,
D3DDEVICEDESC* helDesc,
GUID deviceGuid
);

View File

@ -75,3 +75,31 @@ void FlattenSurfaces(
}
}
}
void Create2DTransformMatrix(
const SDL_Rect& dstRect,
float scale,
float offsetX,
float offsetY,
D3DRMMATRIX4D& outMatrix
)
{
float x = static_cast<float>(dstRect.x) * scale + offsetX;
float y = static_cast<float>(dstRect.y) * scale + offsetY;
float w = static_cast<float>(dstRect.w) * scale;
float h = static_cast<float>(dstRect.h) * scale;
D3DVALUE tmp[4][4] = {{w, 0, 0, 0}, {0, h, 0, 0}, {0, 0, 1, 0}, {x, y, 0, 1}};
memcpy(outMatrix, tmp, sizeof(tmp));
}
void CreateOrthographicProjection(float width, float height, D3DRMMATRIX4D& outProj)
{
D3DVALUE tmp[4][4] = {
{2.0f / width, 0.0f, 0.0f, 0.0f},
{0.0f, -2.0f / height, 0.0f, 0.0f},
{0.0f, 0.0f, 1.0f, 0.0f},
{-1.0f, 1.0f, 0.0f, 1.0f}
};
memcpy(outProj, tmp, sizeof(tmp));
}

View File

@ -13,3 +13,13 @@ void FlattenSurfaces(
std::vector<D3DRMVERTEX>& dedupedVertices,
std::vector<uint16_t>& newIndices
);
void Create2DTransformMatrix(
const SDL_Rect& dstRect,
float scale,
float offsetX,
float offsetY,
D3DRMMATRIX4D& outMatrix
);
void CreateOrthographicProjection(float width, float height, D3DRMMATRIX4D& outProj);

22
packaging/CMakeLists.txt Normal file
View File

@ -0,0 +1,22 @@
set(APP_ID "org.legoisland.Isle")
set(APP_NAME "Isle Portable")
set(APP_SUMMARY "Portable version of the LEGO Island Decompilation Project")
set(APP_SPDX "LGPL-3.0-or-later")
string(TIMESTAMP BUILD_DATE UTC)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/icons)
file(COPY_FILE icons/isle.svg ${CMAKE_BINARY_DIR}/icons/${APP_ID}.svg)
# The following will need to be refined if we wish to post actual releases to a repo such as Flathub
if(DEFINED ENV{GITHUB_ACTIONS} AND ENV{GITHUB_ACTIONS} EQUAL TRUE)
# Use the sequential run# of the current pipeline when running in GH Actions
set(SEMANTIC_VERSION "${PROJECT_VERSION}~build$ENV{GITHUB_RUN_NUMBER}")
else()
# Don't worry about the build number for local builds
set(SEMANTIC_VERSION "${PROJECT_VERSION}")
endif()
if(LINUX)
add_subdirectory(linux)
endif()

161
packaging/icons/isle.svg Normal file
View File

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="180"
height="180"
viewBox="0 0 180 180"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="isle_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
id="title19">LEGO Island Icon</title><sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
inkscape:zoom="3.2222222"
inkscape:cx="99.931034"
inkscape:cy="94.034483"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer2" /><defs
id="defs1" /><g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Sun"
style="display:inline"><circle
style="fill:#ffff00;fill-opacity:1;stroke-width:6.09858"
id="path1"
cx="16.087624"
cy="16.087624"
r="16.087624" /><g
id="g7"
inkscape:label="Rays"
transform="scale(6.0985759)"><path
style="fill:#ffff00;fill-opacity:1;stroke:#ffff00;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 1.512931,6.2456897 V 10.74569"
id="path2" /><path
style="fill:#ffff00;fill-opacity:1;stroke:#ffff00;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 10.922387,1.4939611 h -4.5"
id="path3" /><path
style="fill:#ffff00;fill-opacity:1;stroke:#ffff00;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 3.5838432,6.4674267 4.802595,10.158437"
id="path4" /><path
style="fill:#ffff00;fill-opacity:1;stroke:#ffff00;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 5.4215806,5.3718411 3.20707,2.1962729"
id="path6" /><path
style="fill:#ffff00;fill-opacity:1;stroke:#ffff00;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 6.3519773,3.3961872 3.6866627,1.23184"
id="path7" /></g></g><g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="House"
style="display:inline"><g
id="g11"
inkscape:label="Wall"
style="display:inline"
transform="scale(6.0985759)"><rect
style="fill:#ffff00;fill-opacity:1;stroke:#ffff00;stroke-width:1.31354;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect7"
width="21.557148"
height="2.960166"
x="5.2214251"
y="25.898151" /><rect
style="fill:#ffff00;fill-opacity:1;stroke:#ffff00;stroke-width:1.31354;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect9"
width="21.557148"
height="2.960166"
x="5.2214251"
y="13.077032" /><rect
style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:1.31354;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect10"
width="21.557148"
height="2.960166"
x="5.2214251"
y="21.624445" /><rect
style="fill:#0000ff;fill-opacity:1;stroke:#0000ff;stroke-width:1.31354;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect11"
width="21.557148"
height="2.960166"
x="5.2214251"
y="17.350739" /></g><g
id="g16"
inkscape:label="Window"
transform="scale(6.0985759)"><rect
style="display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.46119;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
id="rect12"
width="9.0243168"
height="11.312326"
x="11.487842"
y="13.257705" /><rect
style="fill:#00ffff;fill-opacity:1;stroke:#00ffff;stroke-width:0.498507;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect13"
width="3.3376997"
height="4.0214062"
x="11.928134"
y="13.989297" /><rect
style="fill:#00ffff;fill-opacity:1;stroke:#00ffff;stroke-width:0.498507;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect14"
width="3.3376997"
height="4.0214062"
x="16.719082"
y="13.989297" /><rect
style="fill:#00ffff;fill-opacity:1;stroke:#00ffff;stroke-width:0.547494;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect15"
width="3.288712"
height="4.9228497"
x="11.952628"
y="19.056892" /><rect
style="fill:#00ffff;fill-opacity:1;stroke:#00ffff;stroke-width:0.547494;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect16"
width="3.288712"
height="4.9228497"
x="16.743576"
y="19.056892" /></g></g><g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Roof"><g
id="g19"
inkscape:label="Antenna"
transform="scale(6.0985759)"><path
style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 21.510776,1.2025862 V 9.1163793"
id="path17" /><path
style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 18.363745,0.526459 6.294062,4.7971783"
id="path18" /><path
style="fill:#ff0000;fill-opacity:1;stroke:#000000;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 18.99097,3.2200622 4.951565,3.7739605"
id="path19" /></g><path
style="display:inline;fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:3.04929;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 19.991237,75.943041 H 173.06023 L 150.40272,51.317332 h -7.63224 l -5.01561,6.189336 h -7.47182 l -4.27253,-8.057106 h -7.58654 l -4.56632,8.057106 h -8.00228 l -4.6922,-8.057106 h -7.441784 l -4.178171,8.057106 h -8.447839 l -3.904314,-8.057106 h -7.225947 l -4.362281,8.057106 h -8.610195 l -5.453345,-6.189336 h -6.082013 z"
id="path16"
sodipodi:nodetypes="ccccccccccccccccccccc"
inkscape:label="Roof" /></g><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:title>LEGO Island Icon</dc:title><dc:date>2025-06-22</dc:date><cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" /></cc:Work><cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /></cc:License></rdf:RDF></metadata></svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,7 @@
# Injects the required variables into the Desktop and MetaInfo files
configure_file(isledecomp.desktop.in "${APP_ID}.desktop" @ONLY)
configure_file(isledecomp.metainfo.xml.in "${APP_ID}.metainfo.xml" @ONLY)
install(FILES "../icons/isle.svg" RENAME "${APP_ID}.svg" DESTINATION "share/icons/hicolor/scalable/apps")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${APP_ID}.desktop" DESTINATION "share/applications")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${APP_ID}.metainfo.xml" DESTINATION "share/metainfo")

25
packaging/linux/appimage/AppRun Executable file
View File

@ -0,0 +1,25 @@
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")"
MAIN=$(grep -r "^Exec=.*" "$HERE"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1)
# MAIN_BIN=$(find "$HERE/usr/bin" -name "$MAIN" | head -n 1)
MAIN_BIN="$HERE/usr/bin/isle-config"
export PATH="${HERE}/usr/bin/":$PATH # Prefer bundled binaries
export QT_QPA_PLATFORMTHEME=xdgdesktopportal # Use XDG filepicker for forward compatability
[ -z "$QT_PLUGIN_PATH" ] && export QT_PLUGIN_PATH=/usr/lib/qt6/plugins:/usr/lib64/qt6/plugins # Use system Qt theme, will fallback to the default one if unavailable
if [ ! -z $APPIMAGE ]; then
BINARY_NAME=$(basename "$ARGV0")
if [ -e "$HERE/usr/bin/$BINARY_NAME" ]; then
exec "$HERE/usr/bin/$BINARY_NAME" "$@"
else
exec "${MAIN_BIN}" "$@"
fi
else
exec "${MAIN_BIN}" "$@"
fi

100
packaging/linux/appimage/Build Executable file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env bash
set -e
export LD_LIBRARY_PATH="build/source/lib:$LD_LIBRARY_PATH"
[ -z "$QMAKE" ] && export QMAKE=/usr/lib/qt6/bin/qmake
# Sets a directory that has to have a following structure:
# build
# ├── bin
# │   ├── isle
# │   └── isle-config
# └── lib
# ├── liblego1.so
# ├── libSDL3.so -> libSDL3.so.0 # Not important if available on the system
# ├── libSDL3.so.0 -> libSDL3.so.0.3.0 # Not important if available on the system
# └── libSDL3.so.0.3.0 # Not important if available on the system
# Can also be defined using --build=path
BUILD_SOURCE=source
# Sets where AppRun for AppImage is, can also be defined using --apprun=path
APPRUN_SOURCE=AppRun
# Sets where desktop file for AppImage is, can also be defined using --desktop-file=path
DESKTOP_FILE_SOURCE=isledecomp.desktop
# You know the drill
ICON_SOURCE=../../icons/isle.svg
cd $(dirname $0)
clean(){
echo "Deleting build directory"
rm -rf build
}
download(){
if [ ! -e "$1" ]; then
curl -Lo "$1" "$2"
fi
}
prepare(){
mkdir -p build/tools
mkdir -p build/assets
download build/tools/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage
chmod u+x build/tools/appimagetool.AppImage
download build/tools/linuxdeploy.AppImage https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-$(uname -m).AppImage
chmod u+x build/tools/linuxdeploy.AppImage
download build/tools/linuxdeploy-plugin-qt.AppImage https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/latest/download/linuxdeploy-plugin-qt-$(uname -m).AppImage
chmod u+x build/tools/linuxdeploy-plugin-qt.AppImage
if [ ! -f "build/assets/isledecomp.desktop" ]; then
cp $DESKTOP_FILE_SOURCE build/assets/isledecomp.desktop
cp $APPRUN_SOURCE build/assets/AppRun
cp ../../icons/isle.svg build/assets/isle.svg
fi
if [ ! -d "build/source" ]; then
cp -r $BUILD_SOURCE build/source
fi
}
compile(){
NO_STRIP=1 build/tools/linuxdeploy.AppImage \
--plugin qt \
-e build/source/bin/isle \
-e build/source/bin/isle-config \
-d build/assets/isledecomp.desktop \
-i build/assets/isle.svg \
--custom-apprun=AppRun \
--appdir=build/AppDir
}
package(){
build/tools/appimagetool.AppImage build/AppDir build/"LEGO_Island-$(uname -m).AppImage"
}
stop(){ # Can be used to do `Build clean stop` to just clean the directory
exit
}
for arg in "$@"; do
case "$arg" in
--build=*) BUILD_SOURCE="${arg#--build=}";;
--apprun=*) APPRUN_SOURCE="${arg#--apprun=}";;
--desktop-file=*) DESKTOP_FILE_SOURCE="${arg#--desktop-file=}";;
--icon=*) ICON_SOURCE="${arg#--icon=}";;
*) "$arg"
esac
done
prepare
compile
package
# Symlinks named as binaries in appimage can call these binaries specifically
# ln -s "LEGO_Island-$(uname -m).AppImage" isle-config
# ln -s "LEGO_Island-$(uname -m).AppImage" isle

View File

@ -0,0 +1,89 @@
{
"id": "org.legoisland.Isle",
"runtime": "org.kde.Platform",
"sdk": "org.kde.Sdk",
"runtime-version": "6.8",
"command": "isle",
"finish-args": [
"--share=ipc",
"--socket=wayland",
"--socket=fallback-x11",
"--socket=pulseaudio",
"--device=dri",
"--device=input",
"--filesystem=/run/media/:ro",
"--filesystem=/media/:ro",
"--filesystem=/mnt/:ro",
"--filesystem=home:ro"
],
"modules": [
{
"name": "isle",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DCMAKE_BUILD_TYPE=RelWithDebInfo",
"-DISLE_DEBUG=OFF"
],
"sources": [
{
"type": "dir",
"path": "../../../3rdparty",
"dest": "3rdparty/"
},
{
"type": "dir",
"path": "../../../cmake",
"dest": "cmake/"
},
{
"type": "dir",
"path": "../../../CMake",
"dest": "CMake/"
},
{
"type": "dir",
"path": "../../../CONFIG",
"dest": "CONFIG/"
},
{
"type": "dir",
"path": "../../../ISLE",
"dest": "ISLE/"
},
{
"type": "dir",
"path": "../../../LEGO1",
"dest": "LEGO1/"
},
{
"type": "dir",
"path": "../../../miniwin",
"dest": "miniwin/"
},
{
"type": "dir",
"path": "../../../packaging",
"dest": "packaging/"
},
{
"type": "dir",
"path": "../../../util",
"dest": "util/"
},
{
"type": "file",
"path": "../../../CMakeLists.txt"
}
],
"build-options": {
"build-args": [
"--share=network"
]
}
}
]
}

View File

@ -0,0 +1,38 @@
[Desktop Entry]
Version=1.5
Name=@APP_NAME@
Comment=@APP_SUMMARY@
Icon=@APP_ID@
Type=Application
Categories=Game;KidsGame;AdventureGame;Qt
Keywords=LEGO;lego;LEGO Island
Keywords[da]=LEGO;lego;Panik på LEGO Øen
Keywords[de]=LEGO;lego;Abenteuer auf der LEGO Insel
Keywords[es]=LEGO;lego;La Isla LEGO
Keywords[fr]=LEGO;lego;Aventures sur L'île LEGO
Keywords[it]=LEGO;lego;Isola LEGO
Keywords[ja]=LEGO;lego;
Keywords[ko]=LEGO;lego;
Keywords[pt]=LEGO;lego;A Ilha LEGO
Keywords[ru]=LEGO;lego;Остров LEGO
Keywords[uk_UA]=LEGO;lego;LEGO острів
SingleMainWindow=true
TryExec=isle
Exec=isle
Actions=play;configure
[Desktop Action play]
Name=Play Game
Icon=currenttrack_play
Exec=isle
[Desktop Action configure]
Name=Configure Settings
Icon=settings
Exec=isle-config

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Freedesktop AppStream metadata file (https://www.freedesktop.org/software/appstream/docs) -->
<component type="desktop-application">
<id>@APP_ID@</id>
<name>@APP_NAME@</name>
<summary>@APP_SUMMARY@</summary>
<launchable type="desktop-id">@APP_ID@.desktop</launchable>
<branding>
<color type="primary">#e3000b</color>
</branding>
<developer id="io.github.isledecomp">
<name>Isle Decomp Team</name>
</developer>
<url type="homepage">https://github.com/isledecomp/isle-portable</url>
<url type="contribute">https://github.com/isledecomp/isle-portable/blob/master/CONTRIBUTING.md</url>
<url type="vcs-browser">https://github.com/isledecomp/isle-portable/tree/master</url>
<url type="bugtracker">https://github.com/isledecomp/isle-portable/issues</url>
<metadata_license>MIT</metadata_license>
<project_license>@APP_SPDX@</project_license>
<requires>
<display_length side="longest" compare="ge">640</display_length>
<internet>offline-only</internet>
</requires>
<recommends>
<memory>128</memory>
</recommends>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>gamepad</control>
</supports>
<description>
<p> This initiative is a portable version of LEGO Island (Version 1.1, English)
based on the decompilation project. Our primary goal is to transform the codebase to achieve
platform independence, thereby enhancing compatibility across various systems while preserving
the original game's experience as faithfully as possible.
</p>
<p>
Please note: this project is dedicated to achieving platform independence without altering the
core gameplay, adding new features, enhancing visual quality, or rewriting code for
improvement's sake. While those are worthwhile objectives, they are not within the scope
of this project.
</p>
</description>
<content_rating type="oars-1.1">
<content_attribute id="violence-cartoon">mild</content_attribute>
<content_attribute id="language-humor">mild</content_attribute>
</content_rating>
<categories>
<category>Game</category>
<category>KidsGame</category>
<category>AdventureGame</category>
<category>Qt</category>
</categories>
<keywords>
<keyword translate="no">LEGO</keyword>
<keyword translate="no">lego</keyword>
<keyword>LEGO Island</keyword>
<!-- Translations sourced from https://www.pcgamingwiki.com/wiki/Lego_Island#Localizations -->
<keyword xml:lang="da">Panik på LEGO Øen</keyword>
<keyword xml:lang="de">Abenteuer auf der LEGO Insel</keyword>
<keyword xml:lang="es">La Isla LEGO</keyword>
<keyword xml:lang="fr">Aventures sur L'île LEGO</keyword>
<keyword xml:lang="it">Isola LEGO</keyword>
<keyword xml:lang="ja">レゴアイランドの大冒険</keyword>
<keyword xml:lang="ko">레고 아일랜드</keyword>
<keyword xml:lang="pt">A Ilha LEGO</keyword>
<keyword xml:lang="ru">Остров LEGO</keyword>
</keywords>
<releases>
<release version="@SEMANTIC_VERSION@" type="development" date="@BUILD_DATE@" />
</releases>
</component>

View File

@ -4,6 +4,7 @@ configureLegoModelPresenter(MxS32): 'DLL exported function'
configureLegoPartPresenter(MxS32, MxS32): 'DLL exported function'
configureLegoROI(int): 'DLL exported function'
configureLegoWorldPresenter(MxS32): 'DLL exported function'
configureMxTransitionManager(TransitionType): 'DLL exported function'
GetNoCD_SourceName(): 'DLL exported function'
m_3dView: 'Allow this variable name'
m_3dManager: 'Allow this variable name'