From 89539a64f17cbd0c21c65aa66cf7af63ffaed924 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 28 Jun 2025 16:28:13 +0200 Subject: [PATCH 01/18] Clear unknown 0x10 in `LegoEntity` (#1593) The naming is a bit weird, as only one bit is used so there are no other usages of this value. --- LEGO1/lego/legoomni/include/legoanimpresenter.h | 2 +- LEGO1/lego/legoomni/include/legoentity.h | 10 +++++----- LEGO1/lego/legoomni/src/entity/legoactor.cpp | 2 +- LEGO1/lego/legoomni/src/entity/legoentity.cpp | 8 ++++---- LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp | 2 +- LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp | 12 ++++++------ 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoanimpresenter.h b/LEGO1/lego/legoomni/include/legoanimpresenter.h index 85917cbc..a3d6cd60 100644 --- a/LEGO1/lego/legoomni/include/legoanimpresenter.h +++ b/LEGO1/lego/legoomni/include/legoanimpresenter.h @@ -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 diff --git a/LEGO1/lego/legoomni/include/legoentity.h b/LEGO1/lego/legoomni/include/legoentity.h index eecf6867..2859b6ac 100644 --- a/LEGO1/lego/legoomni/include/legoentity.h +++ b/LEGO1/lego/legoomni/include/legoentity.h @@ -28,7 +28,7 @@ class LegoEntity : public MxEntity { }; enum { - c_altBit1 = 0x01 + c_disabled = 0x01 }; LegoEntity() { Init(); } @@ -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 diff --git a/LEGO1/lego/legoomni/src/entity/legoactor.cpp b/LEGO1/lego/legoomni/src/entity/legoactor.cpp index c1ce3f74..4b0428b8 100644 --- a/LEGO1/lego/legoomni/src/entity/legoactor.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoactor.cpp @@ -18,7 +18,7 @@ LegoActor::LegoActor() m_frequencyFactor = 0.0f; m_sound = NULL; m_unk0x70 = 0.0f; - m_unk0x10 = 0; + m_interaction = 0; m_actorId = 0; } diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index 467d65d8..b76da503 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -29,7 +29,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; @@ -265,7 +265,7 @@ void LegoEntity::ParseAction(char* p_extra) // FUNCTION: BETA10 0x1007ee87 void LegoEntity::ClickSound(MxBool p_und) { - if (!GetUnknown0x10IsSet(c_altBit1)) { + if (!IsInteraction(c_disabled)) { MxU32 objectId = 0; const char* name = m_roi->GetName(); @@ -297,7 +297,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(); @@ -329,7 +329,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; } } } diff --git a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp index 1c306c1b..fa8158da 100644 --- a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp @@ -691,7 +691,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()) { diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 852263fc..73918606 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -790,7 +790,7 @@ void LegoAnimPresenter::StartingTickle() } FUN_10069b10(); - FUN_1006c8a0(TRUE); + SetDisabled(TRUE); if (m_unk0x78 == NULL) { if (fabs(m_action->GetDirection()[0]) >= 0.00000047683716F || @@ -1090,7 +1090,7 @@ void LegoAnimPresenter::EndAction() } } - FUN_1006c8a0(FALSE); + SetDisabled(FALSE); FUN_1006ab70(); VTable0x90(); @@ -1151,18 +1151,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); } } } From 9dcc701fcb18eb84e7d57ae3454a9391decdba6d Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 28 Jun 2025 20:32:09 +0200 Subject: [PATCH 02/18] Clear unknowns in GetSoundId (#1594) --- LEGO1/lego/legoomni/include/legobuildingmanager.h | 2 +- LEGO1/lego/legoomni/include/legocharactermanager.h | 2 +- LEGO1/lego/legoomni/include/legoentity.h | 14 +++++++------- LEGO1/lego/legoomni/include/legoplantmanager.h | 2 +- .../legoomni/src/common/legobuildingmanager.cpp | 12 ++++++------ .../legoomni/src/common/legocharactermanager.cpp | 12 ++++++------ .../lego/legoomni/src/common/legoplantmanager.cpp | 12 ++++++------ LEGO1/lego/legoomni/src/entity/legoentity.cpp | 8 ++++---- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legobuildingmanager.h b/LEGO1/lego/legoomni/include/legobuildingmanager.h index 3d251c98..c5a69309 100644 --- a/LEGO1/lego/legoomni/include/legobuildingmanager.h +++ b/LEGO1/lego/legoomni/include/legobuildingmanager.h @@ -78,7 +78,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); diff --git a/LEGO1/lego/legoomni/include/legocharactermanager.h b/LEGO1/lego/legoomni/include/legocharactermanager.h index 174db01e..c5a0c242 100644 --- a/LEGO1/lego/legoomni/include/legocharactermanager.h +++ b/LEGO1/lego/legoomni/include/legocharactermanager.h @@ -86,7 +86,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); diff --git a/LEGO1/lego/legoomni/include/legoentity.h b/LEGO1/lego/legoomni/include/legoentity.h index 2859b6ac..a6d9d9ee 100644 --- a/LEGO1/lego/legoomni/include/legoentity.h +++ b/LEGO1/lego/legoomni/include/legoentity.h @@ -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); diff --git a/LEGO1/lego/legoomni/include/legoplantmanager.h b/LEGO1/lego/legoomni/include/legoplantmanager.h index 91fe49c8..c3b2c4e1 100644 --- a/LEGO1/lego/legoomni/include/legoplantmanager.h +++ b/LEGO1/lego/legoomni/include/legoplantmanager.h @@ -49,7 +49,7 @@ class 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); diff --git a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp index d46b8a49..1e4a8b82 100644 --- a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp @@ -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 @@ -548,7 +548,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 +556,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; diff --git a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp index 80b69efc..ff7cf23b 100644 --- a/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp @@ -37,10 +37,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; @@ -931,16 +931,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; diff --git a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp index 766edf79..4e8e2626 100644 --- a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp @@ -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}; @@ -513,16 +513,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; diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index b76da503..9ac9aad6 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -263,7 +263,7 @@ 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 (!IsInteraction(c_disabled)) { MxU32 objectId = 0; @@ -271,15 +271,15 @@ void LegoEntity::ClickSound(MxBool p_und) 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; } From 0982038453babc6233710fea5318b1437516a188 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sun, 29 Jun 2025 00:38:48 +0200 Subject: [PATCH 03/18] Clear unknown in `Hospital` (#1595) --- LEGO1/lego/legoomni/include/hospital.h | 2 +- LEGO1/lego/legoomni/src/worlds/hospital.cpp | 22 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/LEGO1/lego/legoomni/include/hospital.h b/LEGO1/lego/legoomni/include/hospital.h index 4a136568..2a90764b 100644 --- a/LEGO1/lego/legoomni/include/hospital.h +++ b/LEGO1/lego/legoomni/include/hospital.h @@ -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 diff --git a/LEGO1/lego/legoomni/src/worlds/hospital.cpp b/LEGO1/lego/legoomni/src/worlds/hospital.cpp index 4aa4b5a8..dbb1c00c 100644 --- a/LEGO1/lego/legoomni/src/worlds/hospital.cpp +++ b/LEGO1/lego/legoomni/src/worlds/hospital.cpp @@ -46,7 +46,7 @@ Hospital::Hospital() m_flashingLeds = 0; m_copLedAnimTimer = 0; m_pizzaLedAnimTimer = 0; - m_unk0x128 = 0; + m_exited = FALSE; NotificationManager()->Register(this); } @@ -367,8 +367,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); @@ -376,8 +376,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); @@ -410,8 +410,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); @@ -566,8 +566,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; @@ -587,8 +587,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; From 020969c4831819dc766398140cbd9867e8b90a7b Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 28 Jun 2025 17:49:46 -0700 Subject: [PATCH 04/18] Add transition type to ini (#441) --- ISLE/isleapp.cpp | 15 +++++++++++---- ISLE/isleapp.h | 2 ++ LEGO1/lego/legoomni/include/mxtransitionmanager.h | 2 ++ .../legoomni/src/common/mxtransitionmanager.cpp | 9 ++++++++- tools/ncc/skip.yml | 1 + 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index a5091ece..4ee1980f 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -96,11 +96,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 +136,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 @@ -726,6 +723,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,6 +822,7 @@ 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)); iniparser_dump_ini(dict, iniFP); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig); @@ -853,7 +852,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 +885,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) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 2766607f..dd7bfdb1 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -3,6 +3,7 @@ #include "lego1_export.h" #include "legoutils.h" +#include "mxtransitionmanager.h" #include "mxtypes.h" #include "mxvideoparam.h" @@ -91,6 +92,7 @@ class IsleApp { char* m_iniPath; MxFloat m_maxLod; MxU32 m_maxAllowedExtras; + MxTransitionManager::TransitionType m_transitionType; }; extern IsleApp* g_isle; diff --git a/LEGO1/lego/legoomni/include/mxtransitionmanager.h b/LEGO1/lego/legoomni/include/mxtransitionmanager.h index 50b80055..dd9dc9ed 100644 --- a/LEGO1/lego/legoomni/include/mxtransitionmanager.h +++ b/LEGO1/lego/legoomni/include/mxtransitionmanager.h @@ -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' diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index b251c1c0..d54563ab 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -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; +} diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml index 65de1ce7..c5ceef72 100644 --- a/tools/ncc/skip.yml +++ b/tools/ncc/skip.yml @@ -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' From e87184b50227f8fb15957a573da7ca9a2b840fd6 Mon Sep 17 00:00:00 2001 From: David Gow Date: Sun, 29 Jun 2025 23:47:09 +0800 Subject: [PATCH 05/18] Fix the OpenGL backends on non-glx Linux platforms (and remove GLEW dependency) (#446) * Work around issues with depth-buffer size on EGL-based platforms The OpenGL 1.1 and OpenGL ES 2.0 backends can break on EGL-based platforms, such as Wayland, or X11 with SDL_VIDEO_FORCE_EGL=1. One of the reasons for this (the other being glew on the GL1.1 backend) is that SDL/egl get very confused by the way we set OpenGL attributes, particularly SDL_GL_DEPTH_SIZE, resulting in SDL_GL_CreateContext() failing with EGL_BAD_MATCH. The exact cause of this is unknown, but it seems to be a combination of: - SDL_GL_SetAttribute() is supposed to be called _before_ the window is created, and we're calling it afterward. - Creating several test windows during the enumeration process, mixing and matching between OpenGL and OpenGL ES profiles. The "most correct" solution is probably to delay creating the game window until the backend creation process, rather than before the enumeration occurs. But that's a real refactor, which could cause other issues. Instead, set the 24-bit bit depth (which we've hardcoded anyway) before creating the window, and use SDL_GL_ResetAttributes() when creating backends. This seems to work here in all of the cases I was able to try (modulo the GLEW dependency, which is removed in the next patch). * miniwin: Remove GLEW dependency for OpenGL 1.1 GLEW normally backs directly onto glXGetProcAddress on Linux, which is broken on non-GLX setups, such as Wayland (but also X11 with EGL, and presumably KMSDRM). Replace it with manual calls to SDL_GL_GetProcAddress() for the VBO path. Note, however, that SDL_opengl.h includes "windows.h", so conflicts with the miniwin implementation, which breaks builds on windows. In order to work around this, we do what the Direct3D9 implementation does and push all of the OpenGL calls to a separate file, actual.cpp. Going forward, it may make sense to load _all_ OpenGL entry points via SDL, which would allow us to avoid linking directly with libGL/libOpenGL, and therefore eliminate the separate build dependency altogether, as well as allowing more runtime configurability as to the OpenGL library to load. (But that's definitely a bit uglier, and also useful very rarely.) --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- ISLE/isleapp.cpp | 1 + miniwin/CMakeLists.txt | 14 +- miniwin/src/d3drm/backends/opengl1/actual.cpp | 354 ++++++++++++++++++ miniwin/src/d3drm/backends/opengl1/actual.h | 88 +++++ .../src/d3drm/backends/opengl1/renderer.cpp | 298 ++------------- .../src/d3drm/backends/opengles2/renderer.cpp | 8 +- miniwin/src/internal/d3drmrenderer_opengl1.h | 32 +- 9 files changed, 494 insertions(+), 305 deletions(-) create mode 100644 miniwin/src/d3drm/backends/opengl1/actual.cpp create mode 100644 miniwin/src/d3drm/backends/opengl1/actual.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d688877..64c447e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ 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 \ + libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \ libasound2-dev - name: Install macOS dependencies (brew) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e4c6cc9..ef8fdae2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ 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 \ + libxcursor-dev libwayland-dev libxkbcommon-dev wayland-protocols libgl1-mesa-dev qt6-base-dev \ libasound2-dev - name: Install macOS dependencies (brew) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 4ee1980f..e700d5b2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -657,6 +657,7 @@ MxResult IsleApp::SetupWindow() #ifdef MINIWIN 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); diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index a1939441..508e685d 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -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) diff --git a/miniwin/src/d3drm/backends/opengl1/actual.cpp b/miniwin/src/d3drm/backends/opengl1/actual.cpp new file mode 100644 index 00000000..e1c56636 --- /dev/null +++ b/miniwin/src/d3drm/backends/opengl1/actual.cpp @@ -0,0 +1,354 @@ +// This file cannot include any minwin headers. + +#include "actual.h" + +#include "structs.h" + +#include +#include +#include +#include +#include + +// 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(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(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); +} diff --git a/miniwin/src/d3drm/backends/opengl1/actual.h b/miniwin/src/d3drm/backends/opengl1/actual.h new file mode 100644 index 00000000..8a4fb8e8 --- /dev/null +++ b/miniwin/src/d3drm/backends/opengl1/actual.h @@ -0,0 +1,88 @@ +#pragma once + +#include "structs.h" + +#include +#include +#include + +// 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 positions; + std::vector normals; + std::vector texcoords; + std::vector 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); diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index cc415de8..a0b84a80 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -1,5 +1,4 @@ -#include -// must come after GLEW +#include "actual.h" #include "d3drmrenderer_opengl1.h" #include "ddraw_impl.h" #include "ddsurface_impl.h" @@ -11,8 +10,20 @@ #include #include +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); @@ -28,8 +39,6 @@ Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) testWindow = true; } - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GLContext context = SDL_GL_CreateContext(window); if (!context) { SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); @@ -47,18 +56,7 @@ Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) 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); + GL11_InitState(); if (testWindow) { SDL_DestroyWindow(window); @@ -74,7 +72,7 @@ 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() @@ -114,7 +112,7 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t auto* ctx = static_cast(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; } @@ -133,15 +131,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 +147,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 +200,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 +230,7 @@ void OpenGL1Renderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh) auto* ctx = static_cast(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 @@ -329,90 +285,23 @@ const char* OpenGL1Renderer::GetName() 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 +315,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(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(mesh.indices.size()), GL_UNSIGNED_SHORT, mesh.indices.data()); - } - - glPopMatrix(); } HRESULT OpenGL1Renderer::FinalizeFrame() @@ -509,16 +337,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 +357,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(m_viewportTransform.offsetX), diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 3dbabda2..9305510a 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -30,6 +30,12 @@ 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); @@ -41,8 +47,6 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) testWindow = true; } - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GLContext context = SDL_GL_CreateContext(window); if (!context) { if (testWindow) { diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index 71c0ea82..d80621a5 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -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 -#else -#include -#endif - #include #include 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 positions; - std::vector normals; - std::vector texcoords; - std::vector indices; - - // VBO cache - GLuint vboPositions; - GLuint vboNormals; - GLuint vboTexcoords; - GLuint ibo; -}; - class OpenGL1Renderer : public Direct3DRMRenderer { public: static Direct3DRMRenderer* Create(DWORD width, DWORD height); From aa825aeecf44e61a02a240d72986bf49e26315d3 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 29 Jun 2025 08:55:34 -0700 Subject: [PATCH 06/18] Add macro for switchable building index (#1596) --- LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp index 1e4a8b82..4ea96d2b 100644 --- a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp @@ -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) { @@ -461,7 +463,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); From 77bbbfe2ca29c95bfc6f64cc9231b921c446c410 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 29 Jun 2025 09:15:42 -0700 Subject: [PATCH 07/18] (Pepper) Fix building variant switch bug (#451) * (Pepper) Fix building variant switch bug * Add comment --- LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp index 4ea96d2b..8dae5d61 100644 --- a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp @@ -394,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: From 6e7347621c535a3992be40692e950e21a9f6110f Mon Sep 17 00:00:00 2001 From: Brenden Davidson Date: Sun, 29 Jun 2025 14:04:08 -0500 Subject: [PATCH 08/18] Flatpak Build Support (#407) --- .editorconfig | 5 + .gitattributes | 4 + .github/workflows/release.yml | 34 +++- .gitignore | 6 + CMakeLists.txt | 2 + packaging/CMakeLists.txt | 19 +++ packaging/icons/isle.svg | 161 ++++++++++++++++++ packaging/linux/CMakeLists.txt | 7 + .../linux/flatpak/org.legoisland.Isle.json | 89 ++++++++++ packaging/linux/isledecomp.desktop.in | 37 ++++ packaging/linux/isledecomp.metainfo.xml.in | 91 ++++++++++ 11 files changed, 453 insertions(+), 2 deletions(-) create mode 100644 packaging/CMakeLists.txt create mode 100644 packaging/icons/isle.svg create mode 100644 packaging/linux/CMakeLists.txt create mode 100644 packaging/linux/flatpak/org.legoisland.Isle.json create mode 100644 packaging/linux/isledecomp.desktop.in create mode 100644 packaging/linux/isledecomp.metainfo.xml.in diff --git a/.editorconfig b/.editorconfig index c8d511ec..05d09a66 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/.gitattributes b/.gitattributes index 232342c7..04926c6b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef8fdae2..966ef3c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,15 +91,45 @@ jobs: path: | build/dist/isle-* + 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 }} + release: name: 'Release' runs-on: ubuntu-latest - needs: build + needs: + - build + - flatpak steps: - name: Download All Artifacts uses: actions/download-artifact@main with: - pattern: Release-* + pattern: "{Release-*,*.flatpak}" path: Release merge-multiple: true diff --git a/.gitignore b/.gitignore index 662466db..1610ba3d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ LEGO1.DLL # Kate - Text /.cache + +# Flatpak build cache +**/.flatpak-builder/ + +# Flatpak build dir +**/flatpak-build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f7e0595..d77ce732 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -643,6 +643,8 @@ 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}") if(MSVC) diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt new file mode 100644 index 00000000..9994b0c0 --- /dev/null +++ b/packaging/CMakeLists.txt @@ -0,0 +1,19 @@ +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) + +# 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() diff --git a/packaging/icons/isle.svg b/packaging/icons/isle.svg new file mode 100644 index 00000000..d3f6dbf1 --- /dev/null +++ b/packaging/icons/isle.svg @@ -0,0 +1,161 @@ + + + +LEGO Island IconLEGO Island Icon2025-06-22 diff --git a/packaging/linux/CMakeLists.txt b/packaging/linux/CMakeLists.txt new file mode 100644 index 00000000..2ece9db8 --- /dev/null +++ b/packaging/linux/CMakeLists.txt @@ -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") diff --git a/packaging/linux/flatpak/org.legoisland.Isle.json b/packaging/linux/flatpak/org.legoisland.Isle.json new file mode 100644 index 00000000..e8cd3f8e --- /dev/null +++ b/packaging/linux/flatpak/org.legoisland.Isle.json @@ -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" + ] + } + } + ] +} diff --git a/packaging/linux/isledecomp.desktop.in b/packaging/linux/isledecomp.desktop.in new file mode 100644 index 00000000..a40c4a2a --- /dev/null +++ b/packaging/linux/isledecomp.desktop.in @@ -0,0 +1,37 @@ +[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 + +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 diff --git a/packaging/linux/isledecomp.metainfo.xml.in b/packaging/linux/isledecomp.metainfo.xml.in new file mode 100644 index 00000000..b8faecc1 --- /dev/null +++ b/packaging/linux/isledecomp.metainfo.xml.in @@ -0,0 +1,91 @@ + + + + + @APP_ID@ + + @APP_NAME@ + @APP_SUMMARY@ + + @APP_ID@.desktop + + + #e3000b + + + + Isle Decomp Team + + + https://github.com/isledecomp/isle-portable + https://github.com/isledecomp/isle-portable/blob/master/CONTRIBUTING.md + https://github.com/isledecomp/isle-portable/tree/master + https://github.com/isledecomp/isle-portable/issues + + MIT + @APP_SPDX@ + + + 640 + offline-only + + + + 128 + + + + pointing + keyboard + gamepad + + + +

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. +

+ +

+ 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. +

+ +
+ + + mild + mild + + + + Game + KidsGame + AdventureGame + Qt + + + + LEGO + lego + + LEGO Island + + Panik på LEGO Øen + Abenteuer auf der LEGO Insel + La Isla LEGO + Aventures sur L'île LEGO + Isola LEGO + レゴアイランドの大冒険 + 레고 아일랜드 + A Ilha LEGO + Остров LEGO + + + + + +
From 54694a4611ecf78392e71373687bde450186d934 Mon Sep 17 00:00:00 2001 From: Damglador <52221087+Damglador@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:53:54 +0200 Subject: [PATCH 09/18] AppImage packaging (#439) * AppImage packaging * Add flags to specify location of required local files `--build=path` specifies where on the system is the directory with pre-build game binaries (must have binaries `isle` and `isle-config` in `path/bin` and game-specific libraries in `path/lib`) `--apprun=path` specifies where the apprun is `--desktop-file=path` same for the desktop file * Move to packaging/linux * Move building to appimage/build and ignore it in git * Use local icon. Option to specify location for it * Cleaning * Attempt at Github automation * Update CMakeLists.txt * Fix build * I guess it doesn't need quotes * Update CMakeLists.txt * Update release.yml * Work around for liblego1.so loading, fix arguments * Create testing.yml * Update testing.yml * I should pay more attention to what docs say * Fix copy-pasting mistake * Add AppImage packaging to the Release workflow * Try fixing filepicker * Delete testing.yml * Fix releases Can't specify where linuxdeploy leaves the file without specifying the name of the file, which I don't want to do, so just move the file in `dist` after packaging. * Remove unnecessary changes * Add qt6-xdgdesktopportal-platformtheme as deps Needed to call the xdg filepicker, basically desktop-specific filepicker. Hopefully this will allow to use it in AppImage * Get back flatpak in release * Update release.yml * Remove libglew-dev from apt install * Fix duplicate upload artifact * Update release.yml Co-authored-by: Christian Semmler * Remove *.AppImage pattern in Download All Artifacts --------- Co-authored-by: Christian Semmler --- .github/workflows/release.yml | 28 +++++++- packaging/CMakeLists.txt | 3 + packaging/linux/appimage/AppRun | 25 +++++++ packaging/linux/appimage/Build | 100 ++++++++++++++++++++++++++ packaging/linux/isledecomp.desktop.in | 1 + 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100755 packaging/linux/appimage/AppRun create mode 100755 packaging/linux/appimage/Build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 966ef3c6..e5d42a8b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: 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 qt6-base-dev \ - libasound2-dev + libasound2-dev qt6-xdgdesktopportal-platformtheme - name: Install macOS dependencies (brew) if: ${{ matrix.brew }} @@ -84,12 +84,36 @@ jobs: 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: Upload Artifact uses: actions/upload-artifact@main with: name: Release-${{ matrix.name }} path: | build/dist/isle-* + build/dist/*.AppImage flatpak: name: "Flatpak (${{ matrix.arch }})" @@ -118,7 +142,7 @@ jobs: bundle: org.legoisland.Isle.${{ matrix.arch }}.flatpak manifest-path: packaging/linux/flatpak/org.legoisland.Isle.json arch: ${{ matrix.arch }} - + release: name: 'Release' runs-on: ubuntu-latest diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt index 9994b0c0..d6fc19f0 100644 --- a/packaging/CMakeLists.txt +++ b/packaging/CMakeLists.txt @@ -5,6 +5,9 @@ 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 diff --git a/packaging/linux/appimage/AppRun b/packaging/linux/appimage/AppRun new file mode 100755 index 00000000..0da15db1 --- /dev/null +++ b/packaging/linux/appimage/AppRun @@ -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 diff --git a/packaging/linux/appimage/Build b/packaging/linux/appimage/Build new file mode 100755 index 00000000..9cf0325c --- /dev/null +++ b/packaging/linux/appimage/Build @@ -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 \ No newline at end of file diff --git a/packaging/linux/isledecomp.desktop.in b/packaging/linux/isledecomp.desktop.in index a40c4a2a..28309205 100644 --- a/packaging/linux/isledecomp.desktop.in +++ b/packaging/linux/isledecomp.desktop.in @@ -18,6 +18,7 @@ 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 From daa0bd1a32e5f92845817d0e52170dfa9c2da074 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 29 Jun 2025 22:50:28 +0000 Subject: [PATCH 10/18] ci: combine ci and release workflow (#453) --- .github/workflows/ci.yml | 105 ++++++++++++++++++--- .github/workflows/release.yml | 170 ---------------------------------- 2 files changed, 93 insertions(+), 182 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64c447e5..43248276 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,14 +33,14 @@ 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' } steps: - name: Setup vcvars if: ${{ !!matrix.msvc }} @@ -67,7 +67,7 @@ jobs: 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 qt6-base-dev \ - libasound2-dev + libasound2-dev qt6-xdgdesktopportal-platformtheme - name: Install macOS dependencies (brew) if: ${{ matrix.brew }} @@ -89,11 +89,12 @@ jobs: - name: Configure (CMake) run: | ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ - -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ + -DCMAKE_BUILD_TYPE=Release \ -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) @@ -104,11 +105,64 @@ jobs: 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: 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 + + 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 +202,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: "{Release-*,*.flatpak}" + 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/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index e5d42a8b..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,170 +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 qt6-base-dev \ - libasound2-dev qt6-xdgdesktopportal-platformtheme - - - 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: 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: Upload Artifact - uses: actions/upload-artifact@main - with: - name: Release-${{ matrix.name }} - path: | - build/dist/isle-* - build/dist/*.AppImage - - 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 }} - - release: - name: 'Release' - runs-on: ubuntu-latest - needs: - - build - - flatpak - steps: - - name: Download All Artifacts - uses: actions/download-artifact@main - with: - pattern: "{Release-*,*.flatpak}" - 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/* From 79462824e85deb81848ef5242e7a6b7948c43632 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sun, 29 Jun 2025 23:35:24 +0000 Subject: [PATCH 11/18] Fix continuous release (#454) --- .github/workflows/ci.yml | 4 +- CMakeLists.txt | 8 +- cmake/detectcpu.cmake | 156 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 cmake/detectcpu.cmake diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43248276..c6124f37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,7 @@ jobs: ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -GNinja \ -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 \ @@ -214,7 +214,7 @@ jobs: - name: Download All Artifacts uses: actions/download-artifact@main with: - pattern: "{Release-*,*.flatpak}" + pattern: "*" path: Release merge-multiple: true diff --git a/CMakeLists.txt b/CMakeLists.txt index d77ce732..f221d342 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -624,7 +627,8 @@ 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_CPU}") +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() @@ -646,7 +650,7 @@ 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(MSVC) set(CPACK_GENERATOR ZIP) else() diff --git a/cmake/detectcpu.cmake b/cmake/detectcpu.cmake new file mode 100644 index 00000000..e0b6feae --- /dev/null +++ b/cmake/detectcpu.cmake @@ -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} - ") + 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() From a258a89b1f7265efc8240ca0ee2551e3c6d747f8 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Mon, 30 Jun 2025 00:06:21 +0000 Subject: [PATCH 12/18] cmake: fix typo of architecture used in binary artifact (#455) --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f221d342..ac3663fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -627,7 +627,8 @@ else() include(GNUInstallDirs) endif() -string(REPLACE ";" "-" ISLE_CPUS_STRING "${ISLE_CPU}") +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) From 225adda309deaba0d289fc8fa9543737e51dc953 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 30 Jun 2025 22:51:26 +0200 Subject: [PATCH 13/18] Rendering refactoring (#459) * Hint texture intent to renderer * improve transparancy for 32bit rendering * Align OpenGL ES 2.0 with SDL_GPU's 2D rendering --- LEGO1/omni/src/video/mxdisplaysurface.cpp | 20 +- .../src/d3drm/backends/directx9/renderer.cpp | 20 +- .../src/d3drm/backends/opengl1/renderer.cpp | 20 +- .../src/d3drm/backends/opengles2/renderer.cpp | 353 +++++++++--------- .../src/d3drm/backends/sdl3gpu/renderer.cpp | 53 +-- .../src/d3drm/backends/software/renderer.cpp | 20 +- miniwin/src/ddraw/ddraw.cpp | 16 +- miniwin/src/ddraw/framebuffer.cpp | 2 +- miniwin/src/internal/d3drmrenderer.h | 4 +- miniwin/src/internal/d3drmrenderer_directx9.h | 23 +- miniwin/src/internal/d3drmrenderer_opengl1.h | 23 +- .../src/internal/d3drmrenderer_opengles2.h | 38 +- miniwin/src/internal/d3drmrenderer_sdl3gpu.h | 23 +- miniwin/src/internal/d3drmrenderer_software.h | 20 +- miniwin/src/internal/ddraw_impl.h | 9 +- miniwin/src/internal/meshutils.cpp | 28 ++ miniwin/src/internal/meshutils.h | 10 + 17 files changed, 330 insertions(+), 352 deletions(-) diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 6c93a0e7..c2886ebe 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -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); } } diff --git a/miniwin/src/d3drm/backends/directx9/renderer.cpp b/miniwin/src/d3drm/backends/directx9/renderer.cpp index 574939f5..5917cd07 100644 --- a/miniwin/src/d3drm/backends/directx9/renderer.cpp +++ b/miniwin/src/d3drm/backends/directx9/renderer.cpp @@ -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(iTexture); auto surface = static_cast(texture->m_surface); @@ -216,24 +216,6 @@ Uint32 DirectX9Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshG return static_cast(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(); diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index a0b84a80..66f08d23 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -122,7 +122,7 @@ void OpenGL1Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* t ); } -Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture) +Uint32 OpenGL1Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUi) { auto texture = static_cast(iTexture); auto surface = static_cast(texture->m_surface); @@ -265,24 +265,6 @@ 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() { GL11_BeginFrame((Matrix4x4*) &m_projection[0][0]); diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 9305510a..8c39d937 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -181,14 +181,88 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) 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 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(index); + }); + } + + std::vector 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 positions(vertices.size()); + std::transform(vertices.begin(), vertices.end(), positions.begin(), [](const D3DRMVERTEX& v) { + return v.position; + }); + std::vector 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); } OpenGLES2Renderer::~OpenGLES2Renderer() @@ -239,7 +313,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(iTexture); auto surface = static_cast(texture->m_surface); @@ -249,31 +364,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]; @@ -281,85 +383,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 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(index); - }); - } - - std::vector texcoords; - if (meshGroup.texture) { - texcoords.resize(vertices.size()); - std::transform(vertices.begin(), vertices.end(), texcoords.begin(), [](const D3DRMVERTEX& v) { - return v.texCoord; - }); - } - std::vector positions(vertices.size()); - std::transform(vertices.begin(), vertices.end(), positions.begin(), [](const D3DRMVERTEX& v) { - return v.position; - }); - std::vector 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; @@ -411,37 +448,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); @@ -516,8 +527,6 @@ void OpenGLES2Renderer::SubmitDraw( 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); } else { glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 0); @@ -543,7 +552,6 @@ void OpenGLES2Renderer::SubmitDraw( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo); glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDisableVertexAttribArray(posLoc); glDisableVertexAttribArray(normLoc); glDisableVertexAttribArray(texLoc); } @@ -562,7 +570,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); } @@ -584,35 +594,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; @@ -632,17 +613,29 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c glUniform4f(glGetUniformLocation(m_shaderProgram, "u_color"), 1.0f, 1.0f, 1.0f, 1.0f); glUniform1f(glGetUniformLocation(m_shaderProgram, "u_shininess"), 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(dstRect.w) / srcRect.w; + float scaleY = static_cast(dstRect.h) / srcRect.h; + SDL_Rect expandedDstRect = { + static_cast(std::round(dstRect.x - srcRect.x * scaleX)), + static_cast(std::round(dstRect.y - srcRect.y * scaleY)), + static_cast(std::round(texture.width * scaleX)), + static_cast(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]); + glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 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(glGetUniformLocation(m_shaderProgram, "u_normalMatrix"), 1, GL_FALSE, &identity[0][0]); + CreateOrthographicProjection((float) m_width, (float) m_height, projection); glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"), 1, GL_FALSE, &projection[0][0]); glEnable(GL_BLEND); @@ -650,42 +643,34 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c glActiveTexture(GL_TEXTURE0); glUniform1i(glGetUniformLocation(m_shaderProgram, "u_useTexture"), 1); - const GLES2TextureCacheEntry& texture = m_textures[textureId]; 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; - 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 = static_cast(dstRect.x); - float y1 = static_cast(dstRect.y); - float x2 = x1 + dstRect.w; - float y2 = y1 + dstRect.h; - - GLfloat vertices[] = {x1, y1, u1, v1, x2, y1, u2, v1, x1, y2, u1, v2, x2, y2, u2, v2}; + glEnable(GL_SCISSOR_TEST); + glScissor( + static_cast(std::round(dstRect.x * m_viewportTransform.scale + m_viewportTransform.offsetX)), + m_height - static_cast( + std::round((dstRect.y + dstRect.h) * m_viewportTransform.scale + m_viewportTransform.offsetY) + ), + static_cast(std::round(dstRect.w * m_viewportTransform.scale)), + static_cast(std::round(dstRect.h * m_viewportTransform.scale)) + ); GLint posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); - GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); - + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); glEnableVertexAttribArray(posLoc); + glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); glEnableVertexAttribArray(texLoc); + glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), vertices); - glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), vertices + 2); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); + glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - glDisableVertexAttribArray(posLoc); glDisableVertexAttribArray(texLoc); - - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); + glDisable(GL_SCISSOR_TEST); } void OpenGLES2Renderer::Download(SDL_Surface* target) diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index 4ee5a35c..a499193c 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -533,7 +533,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(iTexture); auto surface = static_cast(texture->m_surface); @@ -585,7 +585,7 @@ SDL3MeshCache Direct3DRMSDL3GPURenderer::UploadMesh(const MeshGroup& meshGroup) meshGroup.vertices.size(), meshGroup.indices.data(), meshGroup.indices.size(), - meshGroup.texture != nullptr, + true, finalVertices, newIndices ); @@ -712,24 +712,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) { @@ -940,37 +922,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(dstRect.x) * scale + offsetX; - float y = static_cast(dstRect.y) * scale + offsetY; - float w = static_cast(dstRect.w) * scale; - float h = static_cast(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) { diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 64adc93b..6b969769 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -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(iTexture); auto surface = static_cast(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)) { diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index acf695eb..018fffd1 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -207,14 +207,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); } diff --git a/miniwin/src/ddraw/framebuffer.cpp b/miniwin/src/ddraw/framebuffer.cpp index eb21abef..9f4224fd 100644 --- a/miniwin/src/ddraw/framebuffer.cpp +++ b/miniwin/src/ddraw/framebuffer.cpp @@ -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 = diff --git a/miniwin/src/internal/d3drmrenderer.h b/miniwin/src/internal/d3drmrenderer.h index e76f0759..259b20eb 100644 --- a/miniwin/src/internal/d3drmrenderer.h +++ b/miniwin/src/internal/d3drmrenderer.h @@ -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( diff --git a/miniwin/src/internal/d3drmrenderer_directx9.h b/miniwin/src/internal/d3drmrenderer_directx9.h index b7badb03..900bbe03 100644 --- a/miniwin/src/internal/d3drmrenderer_directx9.h +++ b/miniwin/src/internal/d3drmrenderer_directx9.h @@ -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); } diff --git a/miniwin/src/internal/d3drmrenderer_opengl1.h b/miniwin/src/internal/d3drmrenderer_opengl1.h index d80621a5..58406709 100644 --- a/miniwin/src/internal/d3drmrenderer_opengl1.h +++ b/miniwin/src/internal/d3drmrenderer_opengl1.h @@ -18,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( @@ -57,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); } diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index 7ea1ed9a..432f124e 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -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,10 +62,12 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh); + MeshGroup m_uiMesh; + GLES2MeshCacheEntry m_uiMeshCache; std::vector m_textures; std::vector m_meshs; D3DRMMATRIX4D m_projection; - SDL_Surface* m_renderedImage; + SDL_Surface* m_renderedImage = nullptr; bool m_dirty = false; std::vector m_lights; SDL_GLContext m_context; @@ -78,8 +78,32 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { 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); } diff --git a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h index 25485b1f..02ec05d0 100644 --- a/miniwin/src/internal/d3drmrenderer_sdl3gpu.h +++ b/miniwin/src/internal/d3drmrenderer_sdl3gpu.h @@ -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); } diff --git a/miniwin/src/internal/d3drmrenderer_software.h b/miniwin/src/internal/d3drmrenderer_software.h index 444a80a3..87715999 100644 --- a/miniwin/src/internal/d3drmrenderer_software.h +++ b/miniwin/src/internal/d3drmrenderer_software.h @@ -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); } diff --git a/miniwin/src/internal/ddraw_impl.h b/miniwin/src/internal/ddraw_impl.h index f2604946..4adfb4a5 100644 --- a/miniwin/src/internal/ddraw_impl.h +++ b/miniwin/src/internal/ddraw_impl.h @@ -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 +); diff --git a/miniwin/src/internal/meshutils.cpp b/miniwin/src/internal/meshutils.cpp index 818ecd87..e0ce59e6 100644 --- a/miniwin/src/internal/meshutils.cpp +++ b/miniwin/src/internal/meshutils.cpp @@ -75,3 +75,31 @@ void FlattenSurfaces( } } } + +void Create2DTransformMatrix( + const SDL_Rect& dstRect, + float scale, + float offsetX, + float offsetY, + D3DRMMATRIX4D& outMatrix +) +{ + float x = static_cast(dstRect.x) * scale + offsetX; + float y = static_cast(dstRect.y) * scale + offsetY; + float w = static_cast(dstRect.w) * scale; + float h = static_cast(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)); +} diff --git a/miniwin/src/internal/meshutils.h b/miniwin/src/internal/meshutils.h index fb12a145..12c509ad 100644 --- a/miniwin/src/internal/meshutils.h +++ b/miniwin/src/internal/meshutils.h @@ -13,3 +13,13 @@ void FlattenSurfaces( std::vector& dedupedVertices, std::vector& newIndices ); + +void Create2DTransformMatrix( + const SDL_Rect& dstRect, + float scale, + float offsetX, + float offsetY, + D3DRMMATRIX4D& outMatrix +); + +void CreateOrthographicProjection(float width, float height, D3DRMMATRIX4D& outProj); From ab48ce60b0ccdedfb0053b1ad555e45e16a5a58c Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Mon, 30 Jun 2025 23:38:44 +0200 Subject: [PATCH 14/18] OpenGL ES 2.0 Only lookup shader locations once (#460) --- .../src/d3drm/backends/opengles2/renderer.cpp | 95 +++++++++++-------- .../src/internal/d3drmrenderer_opengles2.h | 12 +++ 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 8c39d937..55719807 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -263,6 +263,23 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co }; 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() @@ -481,12 +498,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; } @@ -508,52 +524,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); + 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(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDisableVertexAttribArray(normLoc); - glDisableVertexAttribArray(texLoc); + glDisableVertexAttribArray(m_normLoc); + glDisableVertexAttribArray(m_texLoc); } HRESULT OpenGLES2Renderer::FinalizeFrame() @@ -605,13 +618,13 @@ 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); const GLES2TextureCacheEntry& texture = m_textures[textureId]; float scaleX = static_cast(dstRect.w) / srcRect.w; @@ -632,19 +645,19 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c modelView ); - glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"), 1, GL_FALSE, &modelView[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(glGetUniformLocation(m_shaderProgram, "u_normalMatrix"), 1, GL_FALSE, &identity[0][0]); + glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]); CreateOrthographicProjection((float) m_width, (float) m_height, projection); - glUniformMatrix4fv(glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"), 1, GL_FALSE, &projection[0][0]); + 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); + glUniform1i(m_useTextureLoc, 1); glBindTexture(GL_TEXTURE_2D, texture.glTextureId); - glUniform1i(glGetUniformLocation(m_shaderProgram, "u_texture"), 0); + glUniform1i(m_textureLoc, 0); glEnable(GL_SCISSOR_TEST); glScissor( @@ -656,20 +669,18 @@ void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, c static_cast(std::round(dstRect.h * m_viewportTransform.scale)) ); - GLint posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); - glEnableVertexAttribArray(posLoc); - glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); - GLint texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.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, m_uiMeshCache.ibo); glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); - glDisableVertexAttribArray(texLoc); + glDisableVertexAttribArray(m_texLoc); glDisable(GL_SCISSOR_TEST); } diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h index 432f124e..f5d62579 100644 --- a/miniwin/src/internal/d3drmrenderer_opengles2.h +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -72,6 +72,18 @@ class OpenGLES2Renderer : public Direct3DRMRenderer { std::vector 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; }; From e2ff65cf91fbf6142f439acea933f58f565ffd3e Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 1 Jul 2025 02:23:13 +0200 Subject: [PATCH 15/18] Expose window during device probing (#462) --- CONFIG/config.cpp | 10 +++- .../legoomni/src/video/legovideomanager.cpp | 2 +- LEGO1/mxdirectx/mxdirectxinfo.cpp | 30 ++++++---- LEGO1/mxdirectx/mxdirectxinfo.h | 3 +- .../src/d3drm/backends/opengl1/renderer.cpp | 36 ++++------- .../src/d3drm/backends/opengles2/renderer.cpp | 30 ++++------ .../src/d3drm/backends/sdl3gpu/renderer.cpp | 60 ++++--------------- 7 files changed, 63 insertions(+), 108 deletions(-) diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index bd38e669..9030ae67 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -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(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; diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index a0173224..7e2f7e42 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -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; } diff --git a/LEGO1/mxdirectx/mxdirectxinfo.cpp b/LEGO1/mxdirectx/mxdirectxinfo.cpp index c5100031..639a2864 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.cpp +++ b/LEGO1/mxdirectx/mxdirectxinfo.cpp @@ -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)); diff --git a/LEGO1/mxdirectx/mxdirectxinfo.h b/LEGO1/mxdirectx/mxdirectxinfo.h index a4ebef91..9bc480de 100644 --- a/LEGO1/mxdirectx/mxdirectxinfo.h +++ b/LEGO1/mxdirectx/mxdirectxinfo.h @@ -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 m_list; // 0x04 unsigned char m_initialized; // 0x10 + HWND m_hWnd; }; // TEMPLATE: BETA10 0x1011c1b0 diff --git a/miniwin/src/d3drm/backends/opengl1/renderer.cpp b/miniwin/src/d3drm/backends/opengl1/renderer.cpp index 66f08d23..fb7fccd4 100644 --- a/miniwin/src/d3drm/backends/opengl1/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengl1/renderer.cpp @@ -28,40 +28,25 @@ Direct3DRMRenderer* OpenGL1Renderer::Create(DWORD width, DWORD height) 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; - } - - SDL_GLContext context = SDL_GL_CreateContext(window); - if (!context) { - SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); - if (testWindow) { - SDL_DestroyWindow(window); - } + if (!DDWindow) { + SDL_Log("No window handler"); return nullptr; } - if (!SDL_GL_MakeCurrent(window, context)) { + SDL_GLContext context = SDL_GL_CreateContext(DDWindow); + if (!context) { + SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); + return nullptr; + } + + 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; } GL11_InitState(); - if (testWindow) { - SDL_DestroyWindow(window); - } - return new OpenGL1Renderer(width, height, context); } @@ -78,6 +63,7 @@ OpenGL1Renderer::OpenGL1Renderer(DWORD width, DWORD height, SDL_GLContext contex OpenGL1Renderer::~OpenGL1Renderer() { SDL_DestroySurface(m_renderedImage); + SDL_GL_DestroyContext(m_context); } void OpenGL1Renderer::PushLights(const SceneLight* lightsArray, size_t count) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index 55719807..d3594298 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -40,25 +40,18 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) 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_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; } @@ -174,10 +167,6 @@ Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height) glDeleteShader(vs); glDeleteShader(fs); - if (testWindow) { - SDL_DestroyWindow(window); - } - return new OpenGLES2Renderer(width, height, context, shaderProgram); } @@ -285,6 +274,7 @@ OpenGLES2Renderer::OpenGLES2Renderer(DWORD width, DWORD height, SDL_GLContext co OpenGLES2Renderer::~OpenGLES2Renderer() { SDL_DestroySurface(m_renderedImage); + SDL_GL_DestroyContext(m_context); glDeleteProgram(m_shaderProgram); } diff --git a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp index a499193c..98704c66 100644 --- a/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp +++ b/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp @@ -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); } @@ -839,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); } From 586327b58447a0ca91cb66196cb8b41c2c7cd3a8 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Tue, 1 Jul 2025 04:15:02 +0200 Subject: [PATCH 16/18] Destry shader before the context (#463) --- miniwin/src/d3drm/backends/opengles2/renderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp index d3594298..6695da81 100644 --- a/miniwin/src/d3drm/backends/opengles2/renderer.cpp +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -274,8 +274,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) From 1ff768935ed25c4a726b2dee2455d97f77c64b66 Mon Sep 17 00:00:00 2001 From: Joshua Peisach Date: Mon, 30 Jun 2025 22:43:10 -0400 Subject: [PATCH 17/18] 3DS Port (#450) * [WIP] 3ds port Recommit of everything after the 2d renderer merge * VERY AWESOME FEATURE FRFR * Stopped CPU suicide and app crashing for now * put in Texture3DS function thing * Fix clear color * Implement 2D rendering via Citro3D * Set 3dsx smdh metadata * Render world content, sort of * Push mesh dynamically * Remove Citro3D init hacks * Clean up Citro3D implementation * Try to upload meshes and convert matricies * Fix 3D rendering * Apply optimizations * Implement lighting * Set 3dsx smdh metadata * Revert "Apply optimizations" This reverts commit 6660082fefb2cebbeeec8428abd8508099f93ad4. * Apply optimizations * Added a cleaner icon (#4) * Fix pure buffer clear frames (#9) * Disable OpenGL on 3DS (#10) * Fix tiled textures and improve UI image quality (#11) * Create 3DS default config overrides * 3ds: implement apt hooks * remove unused import * Apply suggestions from code review Co-authored-by: Christian Semmler Co-authored-by: Anonymous Maarten * Update miniwin/src/d3drm/backends/citro3d/renderer.cpp Co-authored-by: Anonymous Maarten * Separate 3DS apt hook code + move cmake 3ds into ISLE_BUILD_APP * miniwin: use citro3dd if debugging * Optimize texture encoding (#12) * Cleanup * Set correct mipmap level for UI textures (#13) * cpack: include the .3dsx * Add 3DS CI * Fix CI Co-authored-by: Anonymous Maarten * syntax * Refactor c3d renderer (#14) * Refactor c3d renderer * format * Apply suggestions from code review Co-authored-by: Anders Jenbo --------- Co-authored-by: Anders Jenbo * n3ds: just distribute the .3dsx * upload 3dsx * Skip uploading 3DS artifacts * Update ci.yml * Update ci.yml * Remove extraneous ifdef --------- Co-authored-by: MaxBrick Co-authored-by: Anders Jenbo Co-authored-by: Steven <139715581+StevenSYS@users.noreply.github.com> Co-authored-by: Christian Semmler Co-authored-by: Anonymous Maarten --- .github/workflows/ci.yml | 12 + CMakeLists.txt | 22 +- ISLE/3ds/apthooks.cpp | 30 + ISLE/3ds/apthooks.h | 9 + ISLE/3ds/config.cpp | 22 + ISLE/3ds/config.h | 8 + ISLE/isleapp.cpp | 13 +- ISLE/isleapp.h | 1 + ISLE/isledebug.cpp | 2 +- ISLE/res/3ds/isle.png | Bin 0 -> 438 bytes miniwin/CMakeLists.txt | 17 + .../src/d3drm/backends/citro3d/renderer.cpp | 606 ++++++++++++++++++ .../src/d3drm/backends/citro3d/vshader.v.pica | 130 ++++ .../src/d3drm/backends/software/renderer.cpp | 2 +- miniwin/src/d3drm/d3drm.cpp | 8 + miniwin/src/d3drm/d3drmdevice.cpp | 4 + miniwin/src/ddraw/ddraw.cpp | 11 + miniwin/src/internal/d3drmrenderer_citro3d.h | 82 +++ 18 files changed, 975 insertions(+), 4 deletions(-) create mode 100644 ISLE/3ds/apthooks.cpp create mode 100644 ISLE/3ds/apthooks.h create mode 100644 ISLE/3ds/config.cpp create mode 100644 ISLE/3ds/config.h create mode 100644 ISLE/res/3ds/isle.png create mode 100644 miniwin/src/d3drm/backends/citro3d/renderer.cpp create mode 100644 miniwin/src/d3drm/backends/citro3d/vshader.v.pica create mode 100644 miniwin/src/internal/d3drmrenderer_citro3d.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6124f37..57d6e539 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: build: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} + container: ${{ matrix.container || '' }} defaults: run: shell: ${{ matrix.shell || 'sh' }} @@ -41,6 +42,7 @@ 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: '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 }} @@ -89,6 +91,7 @@ jobs: - name: Configure (CMake) run: | ${{ 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 }} \ @@ -101,6 +104,7 @@ jobs: run: cmake --build build --verbose - name: Package (CPack) + if: ${{ !matrix.n3ds }} run: | cd build cpack . @@ -128,6 +132,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: @@ -135,6 +146,7 @@ jobs: path: | build/dist/isle-* build/dist/*.AppImage + build/dist/*.3dsx flatpak: name: "Flatpak (${{ matrix.arch }})" diff --git a/CMakeLists.txt b/CMakeLists.txt index ac3663fc..e9812aea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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" OFF) +cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS" 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") @@ -523,6 +523,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) @@ -652,9 +658,23 @@ add_subdirectory(packaging) set(CPACK_PACKAGE_DIRECTORY "dist") 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 "$/isle.3dsx" DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() if(MSVC) set(CPACK_GENERATOR ZIP) else() set(CPACK_GENERATOR TGZ) endif() + include(CPack) diff --git a/ISLE/3ds/apthooks.cpp b/ISLE/3ds/apthooks.cpp new file mode 100644 index 00000000..3cd9c3e1 --- /dev/null +++ b/ISLE/3ds/apthooks.cpp @@ -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); +} diff --git a/ISLE/3ds/apthooks.h b/ISLE/3ds/apthooks.h new file mode 100644 index 00000000..7bfa30f7 --- /dev/null +++ b/ISLE/3ds/apthooks.h @@ -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 diff --git a/ISLE/3ds/config.cpp b/ISLE/3ds/config.cpp new file mode 100644 index 00000000..dc5dd8dc --- /dev/null +++ b/ISLE/3ds/config.cpp @@ -0,0 +1,22 @@ +#include "config.h" + +#include +#include + +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"); +} diff --git a/ISLE/3ds/config.h b/ISLE/3ds/config.h new file mode 100644 index 00000000..64d3a1b0 --- /dev/null +++ b/ISLE/3ds/config.h @@ -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 diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index e700d5b2..e12f9183 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -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); -#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); @@ -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); diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index dd7bfdb1..d0a7f523 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -96,6 +96,7 @@ class IsleApp { }; extern IsleApp* g_isle; +extern MxS32 g_closed; extern IDirect3DRMMiniwinDevice* GetD3DRMMiniwinDevice(); diff --git a/ISLE/isledebug.cpp b/ISLE/isledebug.cpp index a8950fa3..ec27004b 100644 --- a/ISLE/isledebug.cpp +++ b/ISLE/isledebug.cpp @@ -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); diff --git a/ISLE/res/3ds/isle.png b/ISLE/res/3ds/isle.png new file mode 100644 index 0000000000000000000000000000000000000000..8fd9f2c4ea61bad4ac49ef913f4f0a5eba8a507d GIT binary patch literal 438 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-st^qzFu0WcB;lD8hLnyuVVNQ6as5u0Lg)PKpf|zsSgwsED7=phDalk zhh@*cV_;z9_jGX#@i_i=awp#*1p$}s+rC9ar-uIc?=KpVF@5osD>G(TWHwx=Ol)9n z35ZHkjF|q_IO(6ero KMWF%kqVNX_gEV;R43gF*y7-$W0otxA=crn+jmt*e4du; z@3NDj&!m(NMP1bncyujj;v4pr4l7q1BA3ysTzx`at{3CV2`)-H%tUHVt4y;xA!EC>l{bX2DLD};SUZ9ZjboFyt=akR{ E03U~_mH+?% literal 0 HcmV?d00001 diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 508e685d..851403cc 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -54,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 diff --git a/miniwin/src/d3drm/backends/citro3d/renderer.cpp b/miniwin/src/d3drm/backends/citro3d/renderer.cpp new file mode 100644 index 00000000..f9167401 --- /dev/null +++ b/miniwin/src/d3drm/backends/citro3d/renderer.cpp @@ -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 + +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(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(converted->w * scale); + int scaledH = static_cast(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(iTexture); + auto surface = static_cast(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 vertexBuffer; + std::vector 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 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(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(r * 255) << 24) | (static_cast(g * 255) << 16) | (static_cast(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(dstRect.x); + float y1 = static_cast(dstRect.y); + float x2 = x1 + static_cast(dstRect.w); + float y2 = y1 + static_cast(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(); +} diff --git a/miniwin/src/d3drm/backends/citro3d/vshader.v.pica b/miniwin/src/d3drm/backends/citro3d/vshader.v.pica new file mode 100644 index 00000000..cd7f91c4 --- /dev/null +++ b/miniwin/src/d3drm/backends/citro3d/vshader.v.pica @@ -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 diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp index 6b969769..02c5bfc0 100644 --- a/miniwin/src/d3drm/backends/software/renderer.cpp +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -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) { diff --git a/miniwin/src/d3drm/d3drm.cpp b/miniwin/src/d3drm/d3drm.cpp index e8df37b1..45a4e3ad 100644 --- a/miniwin/src/d3drm/d3drm.cpp +++ b/miniwin/src/d3drm/d3drm.cpp @@ -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); diff --git a/miniwin/src/d3drm/d3drmdevice.cpp b/miniwin/src/d3drm/d3drmdevice.cpp index f6b2f9f7..b738c809 100644 --- a/miniwin/src/d3drm/d3drmdevice.cpp +++ b/miniwin/src/d3drm/d3drmdevice.cpp @@ -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++) { diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index 018fffd1..a3bc5d4d 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -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 @@ -232,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 @@ -346,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); diff --git a/miniwin/src/internal/d3drmrenderer_citro3d.h b/miniwin/src/internal/d3drmrenderer_citro3d.h new file mode 100644 index 00000000..426f75c8 --- /dev/null +++ b/miniwin/src/internal/d3drmrenderer_citro3d.h @@ -0,0 +1,82 @@ +#pragma once + +#include "d3drmrenderer.h" +#include "ddraw_impl.h" + +#include +#include +#include + +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 m_textures; + std::vector m_meshs; + ViewportTransform m_viewportTransform; + std::vector 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); +} From 12d4d6a89aebe800f0e68c9ea6bef2af14035371 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Mon, 30 Jun 2025 19:45:07 -0700 Subject: [PATCH 18/18] Update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 33dbd232..8f5c6756 100644 --- a/README.md +++ b/README.md @@ -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.