Merge remote-tracking branch 'upstream/master'

This commit is contained in:
olebeck 2025-07-02 02:24:36 +02:00
commit fae97351a5
45 changed files with 1287 additions and 152 deletions

View File

@ -25,6 +25,7 @@ jobs:
build:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
container: ${{ matrix.container || '' }}
defaults:
run:
shell: ${{ matrix.shell || 'sh' }}
@ -41,7 +42,8 @@ jobs:
- { 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: 'Vita', os: 'ubuntu-latest', dx5: false, config: false, vita: true, werror: true, clang-tidy: false }
- { 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' }
- { name: 'Vita', os: 'ubuntu-latest', dx5: false, config: false, vita: true, werror: true, clang-tidy: false, cmake-args: '--preset vita'}
steps:
- name: Setup vcvars
if: ${{ !!matrix.msvc }}
@ -101,10 +103,8 @@ jobs:
- name: Configure (CMake)
run: |
if [ "${{ matrix.vita }}" = "true" ]; then
PRESET="--preset vita"
fi
${{ matrix.cmake-wrapper || '' }} cmake $PRESET -S . -B build -GNinja \
${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \
${{ matrix.cmake-args || '' }} \
-DCMAKE_BUILD_TYPE=Release \
-DISLE_USE_DX5=${{ !!matrix.dx5 }} \
-DISLE_BUILD_CONFIG=${{ !!matrix.config }} \
@ -117,6 +117,7 @@ jobs:
run: cmake --build build --verbose
- name: Package (CPack)
if: ${{ !matrix.n3ds }}
run: |
cd build
cpack .
@ -144,6 +145,13 @@ jobs:
--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:
@ -151,6 +159,7 @@ jobs:
path: |
build/dist/isle-*
build/dist/*.AppImage
build/dist/*.3dsx
flatpak:
name: "Flatpak (${{ matrix.arch }})"

55
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Docker
on:
push:
branches: ['master']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}-emscripten
jobs:
publish-emscripten:
name: Publish web port
env:
IMAGE_NAME: ${{ github.repository }}-emscripten
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
file: docker/emscripten/Dockerfile
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

View File

@ -13,7 +13,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
include(CheckCXXSourceCompiles)
include(CMakeDependentOption)
include(CMakePushCheckState)
include(cmake/detectcpu.cmake)
include(CMake/detectcpu.cmake)
DetectTargetCPUArchitectures(ISLE_CPUS)
@ -38,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;NOT VITA" OFF)
cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT VITA" OFF)
cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF)
option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON)
option(ENABLE_CLANG_TIDY "Enable clang-tidy")
@ -542,6 +542,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)
@ -676,12 +682,19 @@ add_subdirectory(packaging)
set(CPACK_PACKAGE_DIRECTORY "dist")
set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-${ISLE_PACKAGE_NAME}")
if(MSVC)
set(CPACK_GENERATOR ZIP)
else()
set(CPACK_GENERATOR TGZ)
endif()
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(VITA)
include("${VITASDK}/share/vita.cmake" REQUIRED)
@ -706,8 +719,12 @@ if(VITA)
NAME ${VITA_APP_NAME}
${VPK_FILE_ARGS}
)
set(CPACK_PACKAGE_FILE_NAME "isle-${PROJECT_VERSION}-vita")
install(FILES "$<TARGET_FILE_DIR:isle>/isle.vpk" DESTINATION "${CMAKE_INSTALL_BINDIR}")
endif()
if(MSVC)
set(CPACK_GENERATOR ZIP)
else()
set(CPACK_GENERATOR TGZ)
endif()
include(CPack)

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
@ -313,6 +318,9 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv)
},
NULL
);
#endif
#ifdef __3DS__
N3DS_SetupAptHooks();
#endif
return SDL_APP_CONTINUE;
}
@ -654,7 +662,7 @@ MxResult IsleApp::SetupWindow()
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, g_targetHeight);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, m_fullScreen);
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, WINDOW_TITLE);
#if defined(MINIWIN) && !defined(__vita__)
#if defined(MINIWIN) && !defined(__3DS__) && !defined(__vita__)
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);
@ -825,6 +833,9 @@ bool IsleApp::LoadConfig()
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);

View File

@ -96,6 +96,7 @@ class IsleApp {
};
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

@ -129,7 +129,7 @@ class Act3 : public LegoWorld {
void RemoveDonut(Act3Ammo& p_p);
MxResult ShootPizza(LegoPathController* p_controller, Vector3& p_location, Vector3& p_direction, Vector3& p_up);
MxResult ShootDonut(LegoPathController* p_controller, Vector3& p_location, Vector3& p_direction, Vector3& p_up);
void FUN_10072ad0(undefined4 p_param1);
void TriggerHitSound(undefined4 p_param1);
MxResult FUN_10073360(Act3Ammo& p_ammo, const Vector3& p_param2);
MxResult FUN_10073390(Act3Ammo& p_ammo, const Vector3& p_param2);
void SetBrickster(Act3Brickster* p_brickster);
@ -168,12 +168,12 @@ class Act3 : public LegoWorld {
Helicopter* m_copter; // 0x420c
Act3Shark* m_shark; // 0x4210
MxFloat m_time; // 0x4214
MxU8 m_unk0x4218; // 0x4218
MxU8 m_unk0x4219; // 0x4219
MxU8 m_unk0x421a; // 0x421a
MxU8 m_unk0x421b; // 0x421b
MxU8 m_unk0x421c; // 0x421c
MxU8 m_unk0x421d; // 0x421d
MxU8 m_pizzaHitSound; // 0x4218
MxU8 m_pizzaMissSound; // 0x4219
MxU8 m_copDonutSound; // 0x421a
MxU8 m_donutMissSound; // 0x421b
MxU8 m_islanderSound; // 0x421c
MxU8 m_bricksterDonutSound; // 0x421d
undefined m_unk0x421e; // 0x421e
Act3List m_unk0x4220; // 0x4220
MxPresenter* m_helicopterDots[15]; // 0x4230

View File

@ -580,7 +580,7 @@ void Act3Brickster::Animate(float p_time)
}
if (m_unk0x54 < p_time) {
((Act3*) m_world)->FUN_10072ad0(5);
((Act3*) m_world)->TriggerHitSound(5);
m_unk0x54 = p_time + 15000.0f;
}
@ -596,7 +596,7 @@ void Act3Brickster::Animate(float p_time)
assert(SoundManager()->GetCacheSoundManager());
if (m_unk0x58 >= 8) {
((Act3*) m_world)->FUN_10072ad0(6);
((Act3*) m_world)->TriggerHitSound(6);
}
else {
SoundManager()->GetCacheSoundManager()->Play("eatpz", NULL, FALSE);

View File

@ -378,11 +378,11 @@ void Act3Ammo::Animate(float p_time)
if (IsBit4()) {
if (IsPizza()) {
m_world->RemovePizza(*this);
m_world->FUN_10072ad0(2);
m_world->TriggerHitSound(2);
}
else {
m_world->RemoveDonut(*this);
m_world->FUN_10072ad0(4);
m_world->TriggerHitSound(4);
}
}
else {

View File

@ -511,7 +511,7 @@ MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id)
}
else {
if (((MxPresenter*) object)->GetAction()) {
FUN_100b7220(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE);
ApplyMask(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE);
}
((MxPresenter*) object)->EndAction();
@ -540,7 +540,7 @@ MxBool RemoveFromWorld(MxAtomId& p_entityAtom, MxS32 p_entityId, MxAtomId& p_wor
}
else {
if (((MxPresenter*) object)->GetAction()) {
FUN_100b7220(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE);
ApplyMask(((MxPresenter*) object)->GetAction(), MxDSAction::c_world, FALSE);
}
((MxPresenter*) object)->EndAction();

View File

@ -46,7 +46,7 @@ MxResult MxControlPresenter::StartAction(MxStreamController* p_controller, MxDSA
{
MxResult result = MxCompositePresenter::StartAction(p_controller, p_action);
FUN_100b7220(m_action, MxDSAction::c_world | MxDSAction::c_looping, TRUE);
ApplyMask(m_action, MxDSAction::c_world | MxDSAction::c_looping, TRUE);
ParseExtra();
MxS16 i = 0;

View File

@ -122,12 +122,12 @@ void LegoWorld::Destroy(MxBool p_fromDestructor)
animPresenter->DecrementUnknown0xd4();
if (animPresenter->GetUnknown0xd4() == 0) {
FUN_100b7220(action, MxDSAction::c_world, FALSE);
ApplyMask(action, MxDSAction::c_world, FALSE);
presenter->EndAction();
}
}
else {
FUN_100b7220(action, MxDSAction::c_world, FALSE);
ApplyMask(action, MxDSAction::c_world, FALSE);
presenter->EndAction();
}
}
@ -143,7 +143,7 @@ void LegoWorld::Destroy(MxBool p_fromDestructor)
MxDSAction* action = presenter->GetAction();
if (action) {
FUN_100b7220(action, MxDSAction::c_world, FALSE);
ApplyMask(action, MxDSAction::c_world, FALSE);
presenter->EndAction();
}
}
@ -159,7 +159,7 @@ void LegoWorld::Destroy(MxBool p_fromDestructor)
MxDSAction* action = presenter->GetAction();
if (action) {
FUN_100b7220(action, MxDSAction::c_world, FALSE);
ApplyMask(action, MxDSAction::c_world, FALSE);
presenter->EndAction();
}
}

View File

@ -189,7 +189,7 @@ MxResult LegoPartPresenter::Read(MxDSChunk& p_chunk)
}
if (j == 0) {
if (surplusLODs != 0 && lod->GetUnknown0x08Test8()) {
if (surplusLODs != 0 && lod->IsExtraLOD()) {
numLODs++;
surplusLODs--;
}

View File

@ -32,7 +32,7 @@ DECOMP_SIZE_ASSERT(Act3ListElement, 0x0c)
DECOMP_SIZE_ASSERT(Act3List, 0x10)
// GLOBAL: LEGO1 0x100d94f8
Act3Script::Script g_unk0x100d94f8[] = {
Act3Script::Script g_pizzaHitSounds[] = {
Act3Script::c_sns02xni_PlayWav,
Act3Script::c_sns03xni_PlayWav,
Act3Script::c_sns04xni_PlayWav,
@ -52,7 +52,7 @@ Act3Script::Script g_unk0x100d94f8[] = {
};
// GLOBAL: LEGO1 0x100d9538
Act3Script::Script g_unk0x100d9538[] = {
Act3Script::Script g_pizzaMissSounds[] = {
Act3Script::c_sns19xni_PlayWav,
Act3Script::c_sns20xni_PlayWav,
Act3Script::c_sns22xni_PlayWav,
@ -62,7 +62,7 @@ Act3Script::Script g_unk0x100d9538[] = {
};
// GLOBAL: LEGO1 0x100d9550
Act3Script::Script g_unk0x100d9550[] = {
Act3Script::Script g_copDonutSounds[] = {
Act3Script::c_sns25xni_PlayWav,
Act3Script::c_sns26xni_PlayWav,
Act3Script::c_sns27xni_PlayWav,
@ -74,7 +74,7 @@ Act3Script::Script g_unk0x100d9550[] = {
};
// GLOBAL: LEGO1 0x100d9570
Act3Script::Script g_unk0x100d9570[] = {
Act3Script::Script g_donutMissSounds[] = {
Act3Script::c_sns30xni_PlayWav,
Act3Script::c_sns31xni_PlayWav,
Act3Script::c_sns32xni_PlayWav,
@ -84,7 +84,7 @@ Act3Script::Script g_unk0x100d9570[] = {
};
// GLOBAL: LEGO1 0x100d9588
Act3Script::Script g_unk0x100d9588[] = {
Act3Script::Script g_islanderSounds[] = {
Act3Script::c_sns43xma_PlayWav, Act3Script::c_sns46xin_PlayWav, Act3Script::c_sns60xna_PlayWav,
Act3Script::c_sns52xro_PlayWav, Act3Script::c_sns58xna_PlayWav, Act3Script::c_sns68xbu_PlayWav,
Act3Script::c_sns59xna_PlayWav, Act3Script::c_sns51xin_PlayWav, Act3Script::c_sns61xva_PlayWav,
@ -95,7 +95,7 @@ Act3Script::Script g_unk0x100d9588[] = {
};
// GLOBAL: LEGO1 0x100d95d8
Act3Script::Script g_unk0x100d95d8[] = {
Act3Script::Script g_bricksterDonutSounds[] = {
Act3Script::c_tns080br_PlayWav,
Act3Script::c_tnsx07br_PlayWav,
Act3Script::c_snsxx2br_PlayWav,
@ -404,58 +404,58 @@ MxResult Act3::ShootDonut(LegoPathController* p_controller, Vector3& p_location,
// FUNCTION: LEGO1 0x10072ad0
// FUNCTION: BETA10 0x10015eec
void Act3::FUN_10072ad0(undefined4 p_param1)
void Act3::TriggerHitSound(undefined4 p_param1)
{
float time = Timer()->GetTime();
Act3Script::Script objectId;
switch (p_param1) {
case 1: {
if (m_unk0x4218 >= sizeOfArray(g_unk0x100d94f8)) {
m_unk0x4218 = 0;
if (m_pizzaHitSound >= sizeOfArray(g_pizzaHitSounds)) {
m_pizzaHitSound = 0;
}
objectId = g_unk0x100d94f8[m_unk0x4218++];
objectId = g_pizzaHitSounds[m_pizzaHitSound++];
break;
}
case 2: {
if (m_unk0x4219 >= sizeOfArray(g_unk0x100d9538) - 1) {
m_unk0x4219 = 0;
if (m_pizzaMissSound >= sizeOfArray(g_pizzaMissSounds) - 1) {
m_pizzaMissSound = 0;
}
objectId = g_unk0x100d9538[m_unk0x4219++];
objectId = g_pizzaMissSounds[m_pizzaMissSound++];
break;
}
case 3: {
if (m_unk0x421a >= sizeOfArray(g_unk0x100d9550)) {
m_unk0x421a = 0;
if (m_copDonutSound >= sizeOfArray(g_copDonutSounds)) {
m_copDonutSound = 0;
}
objectId = g_unk0x100d9550[m_unk0x421a++];
objectId = g_copDonutSounds[m_copDonutSound++];
break;
}
case 4: {
if (m_unk0x421b >= sizeOfArray(g_unk0x100d9570)) {
m_unk0x421b = 0;
if (m_donutMissSound >= sizeOfArray(g_donutMissSounds)) {
m_donutMissSound = 0;
}
objectId = g_unk0x100d9570[m_unk0x421b++];
objectId = g_donutMissSounds[m_donutMissSound++];
break;
}
case 5: {
if (m_unk0x421c >= sizeOfArray(g_unk0x100d9588)) {
m_unk0x421c = 0;
if (m_islanderSound >= sizeOfArray(g_islanderSounds)) {
m_islanderSound = 0;
}
objectId = g_unk0x100d9588[m_unk0x421c++];
objectId = g_islanderSounds[m_islanderSound++];
break;
}
case 6: {
if (m_unk0x421d >= sizeOfArray(g_unk0x100d95d8)) {
m_unk0x421d = 0;
if (m_bricksterDonutSound >= sizeOfArray(g_bricksterDonutSounds)) {
m_bricksterDonutSound = 0;
}
m_unk0x4220.Insert(g_unk0x100d95d8[m_unk0x421d++], 1);
m_unk0x4220.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], 1);
return;
}
default:
@ -576,13 +576,12 @@ MxLong Act3::Notify(MxParam& p_param)
m_cop2->VTable0xa8();
m_brickster->VTable0xa8();
m_unk0x4218 = 0;
m_unk0x4219 = 0;
m_unk0x421a = 0;
m_unk0x421b = 0;
m_unk0x421c = 0;
m_unk0x421d = 0;
m_pizzaHitSound = 0;
m_pizzaMissSound = 0;
m_copDonutSound = 0;
m_donutMissSound = 0;
m_islanderSound = 0;
m_bricksterDonutSound = 0;
MxS32 length;
LegoBuildingInfo* info = BuildingManager()->GetInfoArray(length);
@ -702,7 +701,7 @@ MxResult Act3::FUN_10073360(Act3Ammo& p_ammo, const Vector3& p_param2)
{
assert(m_brickster);
m_brickster->FUN_100417a0(p_ammo, p_param2);
FUN_10072ad0(1);
TriggerHitSound(1);
return SUCCESS;
}
@ -719,7 +718,7 @@ MxResult Act3::FUN_10073390(Act3Ammo& p_ammo, const Vector3& p_param2)
m_cop2->FUN_10040350(p_ammo, p_param2);
}
FUN_10072ad0(3);
TriggerHitSound(3);
g_unk0x100f7814++;
return SUCCESS;
}

View File

@ -1457,7 +1457,7 @@ void Infocenter::StartCredits()
MxDSAction* action = presenter->GetAction();
if (action) {
FUN_100b7220(action, MxDSAction::c_world, FALSE);
ApplyMask(action, MxDSAction::c_world, FALSE);
presenter->EndAction();
}
}
@ -1473,7 +1473,7 @@ void Infocenter::StartCredits()
MxDSAction* action = presenter->GetAction();
if (action) {
FUN_100b7220(action, MxDSAction::c_world, FALSE);
ApplyMask(action, MxDSAction::c_world, FALSE);
presenter->EndAction();
}
}

View File

@ -71,11 +71,11 @@ LegoResult LegoLOD::Read(Tgl::Renderer* p_renderer, LegoTextureContainer* p_text
LegoU32 i, indexBackwards, indexForwards, tempNumVertsAndNormals;
unsigned char paletteEntries[256];
if (p_storage->Read(&m_unk0x08, sizeof(undefined4)) != SUCCESS) {
if (p_storage->Read(&m_flags, sizeof(LegoU32)) != SUCCESS) {
goto done;
}
if (GetUnknown0x08Test4()) {
if (SkipReadingData()) {
return SUCCESS;
}
@ -86,11 +86,11 @@ LegoResult LegoLOD::Read(Tgl::Renderer* p_renderer, LegoTextureContainer* p_text
}
if (m_numMeshes == 0) {
ClearFlag(c_bit4);
ClearFlag(c_hasMesh);
return SUCCESS;
}
SetFlag(c_bit4);
SetFlag(c_hasMesh);
m_melems = new Mesh[m_numMeshes];
memset(m_melems, 0, sizeof(*m_melems) * m_numMeshes);
@ -317,7 +317,7 @@ LegoLOD* LegoLOD::Clone(Tgl::Renderer* p_renderer)
dupLod->m_melems[i].m_textured = m_melems[i].m_textured;
}
dupLod->m_unk0x08 = m_unk0x08;
dupLod->m_flags = m_flags;
dupLod->m_numMeshes = m_numMeshes;
dupLod->m_numVertices = m_numVertices;
dupLod->m_numPolys = m_numPolys;

View File

@ -260,7 +260,7 @@ LegoResult LegoROI::Read(
}
if (j == 0) {
if (surplusLODs != 0 && lod->GetUnknown0x08Test8()) {
if (surplusLODs != 0 && lod->IsExtraLOD()) {
numLODs++;
}
}
@ -277,7 +277,7 @@ LegoResult LegoROI::Read(
}
if (i == 0) {
if (surplusLODs != 0 && lod->GetUnknown0x08Test8()) {
if (surplusLODs != 0 && lod->IsExtraLOD()) {
numLODs++;
}
}

View File

@ -92,7 +92,7 @@ void MakeSourceName(char*, const char*);
void OmniError(const char* p_message, MxS32 p_status);
void SetOmniUserMessage(void (*p_omniUserMessage)(const char*, MxS32));
MxBool ContainsPresenter(MxCompositePresenterList& p_presenterList, MxPresenter* p_presenter);
void FUN_100b7220(MxDSAction* p_action, MxU32 p_newFlags, MxBool p_setFlags);
void ApplyMask(MxDSAction* p_action, MxU32 p_mask, MxBool p_setFlags);
MxBool KeyValueStringParse(char*, const char*, const char*);
// TEMPLATE: BETA10 0x1012dfd0

View File

@ -153,16 +153,16 @@ void SetOmniUserMessage(void (*p_omniUserMessage)(const char*, MxS32))
// FUNCTION: LEGO1 0x100b7220
// FUNCTION: BETA10 0x10136f37
void FUN_100b7220(MxDSAction* p_action, MxU32 p_newFlags, MxBool p_setFlags)
void ApplyMask(MxDSAction* p_action, MxU32 p_mask, MxBool p_setFlags)
{
MxU32 oldFlags = p_action->GetFlags();
MxU32 newFlags;
if (p_setFlags) {
newFlags = oldFlags | p_newFlags;
newFlags = oldFlags | p_mask;
}
else {
newFlags = oldFlags & ~p_newFlags;
newFlags = oldFlags & ~p_mask;
}
p_action->SetFlags(newFlags);
@ -172,7 +172,7 @@ void FUN_100b7220(MxDSAction* p_action, MxU32 p_newFlags, MxBool p_setFlags)
MxDSAction* action;
while (cursor.Next(action)) {
FUN_100b7220(action, p_newFlags, p_setFlags);
ApplyMask(action, p_mask, p_setFlags);
}
}
}

View File

@ -14,10 +14,10 @@
class ViewLOD : public LODObject {
public:
enum {
c_bit4 = 0x10
c_hasMesh = 0x10
};
ViewLOD(Tgl::Renderer* pRenderer) : m_meshBuilder(NULL), m_unk0x08(3) {}
ViewLOD(Tgl::Renderer* pRenderer) : m_meshBuilder(NULL), m_flags(3) {}
~ViewLOD() override;
// FUNCTION: LEGO1 0x100a6f30
@ -28,19 +28,19 @@ class ViewLOD : public LODObject {
Tgl::MeshBuilder* GetMeshBuilder() { return m_meshBuilder; }
const Tgl::MeshBuilder* GetMeshBuilder() const { return m_meshBuilder; }
undefined4 GetUnknown0x08() { return m_unk0x08; }
unsigned char GetUnknown0x08Test4() { return m_unk0x08 & 0xffffff04; }
unsigned char GetUnknown0x08Test8() { return m_unk0x08 & 0xffffff08; }
unsigned int GetFlags() { return m_flags; }
unsigned char SkipReadingData() { return m_flags & 0xffffff04; }
unsigned char IsExtraLOD() { return m_flags & 0xffffff08; }
void SetFlag(unsigned char p_flag) { m_unk0x08 |= p_flag; }
void ClearFlag(unsigned char p_flag) { m_unk0x08 &= ~p_flag; }
void SetFlag(unsigned char p_flag) { m_flags |= p_flag; }
void ClearFlag(unsigned char p_flag) { m_flags &= ~p_flag; }
// SYNTHETIC: LEGO1 0x100a6f60
// ViewLOD::`scalar deleting destructor'
protected:
Tgl::MeshBuilder* m_meshBuilder; // 0x04
undefined4 m_unk0x08; // 0x08
unsigned int m_flags; // 0x08
};
#endif // VIEWLOD_H

View File

@ -165,7 +165,7 @@ void ViewManager::UpdateROIDetailBasedOnLOD(ViewROI* p_roi, int p_lodLevel)
if (lodLevel < 0) {
lod = (ViewLOD*) p_roi->GetLOD(p_lodLevel);
if (lod->GetUnknown0x08() & ViewLOD::c_bit4) {
if (lod->GetFlags() & ViewLOD::c_hasMesh) {
scene->Add(group);
SetAppData(p_roi, reinterpret_cast<LPD3DRM_APPDATA>(p_roi));
}
@ -184,7 +184,7 @@ void ViewManager::UpdateROIDetailBasedOnLOD(ViewROI* p_roi, int p_lodLevel)
lod = (ViewLOD*) p_roi->GetLOD(p_lodLevel);
}
if (lod->GetUnknown0x08() & ViewLOD::c_bit4) {
if (lod->GetFlags() & ViewLOD::c_hasMesh) {
meshBuilder = lod->GetMeshBuilder();
if (meshBuilder != NULL) {
@ -389,7 +389,7 @@ inline int ViewManager::GetFirstLODIndex(ViewROI* p_roi)
const LODListBase* lods = p_roi->GetLODs();
if (lods != NULL && lods->Size() > 0) {
if (((ViewLOD*) p_roi->GetLOD(0))->GetUnknown0x08Test8()) {
if (((ViewLOD*) p_roi->GetLOD(0))->IsExtraLOD()) {
return 1;
}
else {
@ -404,7 +404,7 @@ inline int ViewManager::GetFirstLODIndex(ViewROI* p_roi)
const LODListBase* lods = ((ViewROI*) *it)->GetLODs();
if (lods != NULL && lods->Size() > 0) {
if (((ViewLOD*) ((ViewROI*) *it)->GetLOD(0))->GetUnknown0x08Test8()) {
if (((ViewLOD*) ((ViewROI*) *it)->GetLOD(0))->IsExtraLOD()) {
return 1;
}
else {

View File

@ -8,18 +8,26 @@ Please note: this project is dedicated to achieving platform independence withou
## Status
### Supported platforms
| Platform | Status |
| - | - |
| Windows | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) |
| 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.
### Library substitutions
## Usage
**An existing copy of LEGO Island is required to use this project.**
As it stands, builds provided in the [Releases tab](https://github.com/isledecomp/isle-portable/releases/tag/continuous) are mainly for developers; as such, they may not work properly for all end-users. Work is currently ongoing to create workable release builds ready for gameplay and general use by end-users. If you are technically inclined, you may find it easiest to compile the project yourself to get it running at this current point in time.
[Installation instructions](https://github.com/isledecomp/isle-portable/wiki/Installation) for some ports can be found in our Wiki.
## Library substitutions
To achieve our goal of platform independence, we need to replace any Windows-only libraries with platform-independent alternatives. This ensures that our codebase remains versatile and compatible across various systems. The following table serves as an overview of major libraries / subsystems and their chosen replacements. For any significant changes or additions, it's recommended to discuss them with the team on the Matrix chat first to ensure consistency and alignment with our project's objectives.
@ -42,12 +50,6 @@ To achieve our goal of platform independence, we need to replace any Windows-onl
This project uses the [CMake](https://cmake.org/) build system, which allows for a high degree of versatility regarding compilers and development environments. Please refer to the [GitHub action](/.github/workflows//ci.yml) for guidance.
## Usage
**An existing copy of LEGO Island is required to use this project.**
As it stands, the builds provided in the Releases tab are for developers; as such, they may not work properly for end-users. Work is currently ongoing to create workable release builds ready for gameplay and general use by end-users. If you are technically inclined, you may find it easiest to compile the project yourself to get it running at this current point in time.
## Contributing
If you're interested in helping or contributing to this project, check out the [CONTRIBUTING](/CONTRIBUTING.md) page.

View File

@ -0,0 +1,37 @@
FROM emscripten/emsdk:latest AS builder
ARG CMAKE_VERSION=3.29.3
WORKDIR /src
USER root
RUN apt-get update && apt-get install -y git wget && rm -rf /var/lib/apt/lists/*
RUN wget https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-Linux-x86_64.sh -O /tmp/cmake.sh && \
chmod +x /tmp/cmake.sh && \
/tmp/cmake.sh --skip-license --prefix=/usr/local && \
rm /tmp/cmake.sh
RUN chown -R emscripten:emscripten /src
USER emscripten
COPY ISLE/emscripten/libwasmfs_fetch.js.patch /tmp/
RUN cd /emsdk/upstream/emscripten && \
git apply --check /tmp/libwasmfs_fetch.js.patch && \
git apply /tmp/libwasmfs_fetch.js.patch
COPY --chown=emscripten:emscripten . .
RUN emcmake cmake -S . -B build -DISLE_BUILD_CONFIG=OFF -DISLE_DEBUG=OFF -DCMAKE_BUILD_TYPE=Release -DISLE_EMSCRIPTEN_HOST=/assets && \
emmake cmake --build build -j 32
RUN echo "Fetching isle.pizza frontend..."; \
git clone --depth 1 https://github.com/isledecomp/isle.pizza /tmp/isle.pizza;
FROM nginx:alpine
COPY docker/emscripten/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /tmp/isle.pizza /usr/share/nginx/html
COPY --from=builder /src/build/isle.* /usr/share/nginx/html
EXPOSE 6931

View File

@ -0,0 +1,27 @@
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
server {
listen 6931;
server_name localhost;
add_header 'Cross-Origin-Embedder-Policy' 'require-corp';
add_header 'Cross-Origin-Opener-Policy' 'same-origin';
add_header 'Cross-Origin-Resource-Policy' 'cross-origin';
location / {
root /usr/share/nginx/html;
index index.html isle.html;
try_files $uri $uri/ =404;
}
location ~* ^/assets/(.*)$ {
alias /assets/;
try_files /$1 /DATA/disk/$1 =404;
}
}
}

View File

@ -78,6 +78,23 @@ if(VITA)
#target_link_directories(miniwin PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/d3drm/backends/gxm)
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

@ -42,12 +42,26 @@ void GL11_DestroyTexture(GLuint texId)
glDeleteTextures(1, &texId);
}
GLuint GL11_UploadTextureData(void* pixels, int width, int height)
GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUi)
{
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
if (isUi) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
return texId;
}
@ -111,7 +125,6 @@ void GL11_BeginFrame(const Matrix4x4* projection)
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) {
@ -198,6 +211,7 @@ void GL11_SubmitDraw(
glLoadMatrixf(&modelViewMatrix[0][0]);
glEnable(GL_NORMALIZE);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glColor4ub(appearance.color.r, appearance.color.g, appearance.color.b, appearance.color.a);
if (appearance.shininess != 0.0f) {
@ -220,8 +234,6 @@ void GL11_SubmitDraw(
// 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);
@ -279,7 +291,7 @@ void GL11_Clear(float r, float g, float b)
}
void GL11_Draw2DImage(
GLuint texId,
GLTextureCacheEntry& cache,
const SDL_Rect& srcRect,
const SDL_Rect& dstRect,
float left,
@ -305,24 +317,17 @@ void GL11_Draw2DImage(
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texId);
glBindTexture(GL_TEXTURE_2D, cache.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 u1 = srcRect.x / cache.width;
float v1 = srcRect.y / cache.height;
float u2 = (srcRect.x + srcRect.w) / cache.width;
float v2 = (srcRect.y + srcRect.h) / cache.height;
float x1 = (float) dstRect.x;
float y1 = (float) dstRect.y;

View File

@ -39,6 +39,8 @@ struct GLTextureCacheEntry {
IDirect3DRMTexture* texture;
Uint32 version;
GLuint glTextureId;
float width;
float height;
};
struct GLMeshCacheEntry {
@ -62,7 +64,7 @@ struct GLMeshCacheEntry {
void GL11_InitState();
void GL11_LoadExtensions();
void GL11_DestroyTexture(GLuint texId);
GLuint GL11_UploadTextureData(void* pixels, int width, int height);
GLuint GL11_UploadTextureData(void* pixels, int width, int height, bool isUI);
void GL11_UploadMesh(GLMeshCacheEntry& cache, bool hasTexture);
void GL11_DestroyMesh(GLMeshCacheEntry& cache);
void GL11_BeginFrame(const Matrix4x4* projection);
@ -77,7 +79,7 @@ void GL11_SubmitDraw(
void GL11_Resize(int width, int height);
void GL11_Clear(float r, float g, float b);
void GL11_Draw2DImage(
GLuint texId,
GLTextureCacheEntry& cache,
const SDL_Rect& srcRect,
const SDL_Rect& dstRect,
float left,

View File

@ -58,6 +58,8 @@ OpenGL1Renderer::OpenGL1Renderer(DWORD width, DWORD height, SDL_GLContext contex
m_virtualHeight = height;
m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32);
GL11_LoadExtensions();
m_useVBOs = SDL_GL_ExtensionSupported("GL_ARB_vertex_buffer_object");
m_useNPOT = SDL_GL_ExtensionSupported("GL_OES_texture_npot");
}
OpenGL1Renderer::~OpenGL1Renderer()
@ -108,6 +110,58 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t
);
}
static int NextPowerOfTwo(int v)
{
int power = 1;
while (power < v) {
power <<= 1;
}
return power;
}
static Uint32 UploadTextureData(SDL_Surface* src, bool useNPOT, bool isUi)
{
SDL_Surface* working = src;
if (src->format != SDL_PIXELFORMAT_RGBA32) {
working = SDL_ConvertSurface(src, SDL_PIXELFORMAT_RGBA32);
if (!working) {
SDL_Log("SDL_ConvertSurface failed: %s", SDL_GetError());
return NO_TEXTURE_ID;
}
}
SDL_Surface* finalSurface = working;
int newW = NextPowerOfTwo(working->w);
int newH = NextPowerOfTwo(working->h);
if (!useNPOT && (newW != working->w || newH != working->h)) {
SDL_Surface* resized = SDL_CreateSurface(newW, newH, working->format);
if (!resized) {
SDL_Log("SDL_CreateSurface (resize) failed: %s", SDL_GetError());
if (working != src) {
SDL_DestroySurface(working);
}
return NO_TEXTURE_ID;
}
SDL_Rect srcRect = {0, 0, working->w, working->h};
SDL_Rect dstRect = {0, 0, newW, newH};
SDL_BlitSurfaceScaled(working, &srcRect, resized, &dstRect, SDL_SCALEMODE_NEAREST);
if (working != src) {
SDL_DestroySurface(working);
}
finalSurface = resized;
}
Uint32 texId = GL11_UploadTextureData(finalSurface->pixels, finalSurface->w, finalSurface->h, isUi);
if (finalSurface != src) {
SDL_DestroySurface(finalSurface);
}
return texId;
}
Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
@ -118,28 +172,16 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
if (tex.texture == texture) {
if (tex.version != texture->m_version) {
GL11_DestroyTexture(tex.glTextureId);
SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return NO_TEXTURE_ID;
}
tex.glTextureId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h);
SDL_DestroySurface(surf);
tex.glTextureId = UploadTextureData(surface->m_surface, m_useNPOT, isUi);
tex.version = texture->m_version;
tex.width = surface->m_surface->w;
tex.height = surface->m_surface->h;
}
return i;
}
}
GLuint texId;
SDL_Surface* surf = SDL_ConvertSurface(surface->m_surface, SDL_PIXELFORMAT_RGBA32);
if (!surf) {
return NO_TEXTURE_ID;
}
texId = GL11_UploadTextureData(surf->pixels, surf->w, surf->h);
SDL_DestroySurface(surf);
GLuint texId = UploadTextureData(surface->m_surface, m_useNPOT, isUi);
for (Uint32 i = 0; i < m_textures.size(); ++i) {
auto& tex = m_textures[i];
@ -147,12 +189,20 @@ Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi)
tex.texture = texture;
tex.version = texture->m_version;
tex.glTextureId = texId;
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});
m_textures.push_back(
{texture,
texture->m_version,
texId,
static_cast<float>(surface->m_surface->w),
static_cast<float>(surface->m_surface->h)}
);
AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture);
return (Uint32) (m_textures.size() - 1);
}
@ -331,7 +381,7 @@ void OpenGL1Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, con
float top = -m_viewportTransform.offsetY / m_viewportTransform.scale;
float bottom = (m_height - m_viewportTransform.offsetY) / m_viewportTransform.scale;
GL11_Draw2DImage(m_textures[textureId].glTextureId, srcRect, dstRect, left, right, bottom, top);
GL11_Draw2DImage(m_textures[textureId], srcRect, dstRect, left, right, bottom, top);
}
void OpenGL1Renderer::Download(SDL_Surface* target)

View File

@ -47,6 +47,7 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height)
SDL_GLContext context = SDL_GL_CreateContext(DDWindow);
if (!context) {
SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError());
return nullptr;
}
@ -274,8 +275,8 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co
OpenGLES2Renderer::~OpenGLES2Renderer()
{
SDL_DestroySurface(m_renderedImage);
SDL_GL_DestroyContext(m_context);
glDeleteProgram(m_shaderProgram);
SDL_GL_DestroyContext(m_context);
}
void OpenGLES2Renderer::PushLights(const SceneLight* lightsArray, size_t count)

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) {

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
@ -172,6 +175,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
@ -241,6 +244,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
@ -362,6 +368,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

@ -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

@ -46,6 +46,7 @@ class OpenGL1Renderer : public Direct3DRMRenderer {
D3DRMMATRIX4D m_projection;
SDL_Surface* m_renderedImage;
bool m_useVBOs;
bool m_useNPOT;
bool m_dirty = false;
std::vector<SceneLight> m_lights;
SDL_GLContext m_context;

View File

@ -1,12 +1,9 @@
{
"id": "org.legoisland.Isle",
"runtime": "org.kde.Platform",
"sdk": "org.kde.Sdk",
"runtime-version": "6.8",
"command": "isle",
"finish-args": [
"--share=ipc",
"--socket=wayland",
@ -19,7 +16,6 @@
"--filesystem=/mnt/:ro",
"--filesystem=home:ro"
],
"modules": [
{
"name": "isle",
@ -34,11 +30,6 @@
"path": "../../../3rdparty",
"dest": "3rdparty/"
},
{
"type": "dir",
"path": "../../../cmake",
"dest": "cmake/"
},
{
"type": "dir",
"path": "../../../CMake",
@ -86,4 +77,4 @@
}
}
]
}
}