mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 16:21:04 +02:00
316 lines
8.1 KiB
C++
316 lines
8.1 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
IceTech GPL Source Code
|
|
Copyright (C) 2026 Justin Marshall
|
|
|
|
This file is part of the IceTech GPL Source Code.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "MinigameHost.h"
|
|
#include "../renderer/RenderSystem.h"
|
|
|
|
idMinigameInstance::idMinigameInstance() {
|
|
moduleHandle = 0;
|
|
memset( &exports, 0, sizeof( exports ) );
|
|
memset( &imports, 0, sizeof( imports ) );
|
|
instance = NULL;
|
|
memset( &threadInfo, 0, sizeof( threadInfo ) );
|
|
InitializeCriticalSection( &criticalSection );
|
|
threadStarted = false;
|
|
shutdownRequested = false;
|
|
active = false;
|
|
activeSent = false;
|
|
failed = false;
|
|
frameReady = false;
|
|
framebuffer = NULL;
|
|
framebufferWidth = 0;
|
|
framebufferHeight = 0;
|
|
framebufferBytes = 0;
|
|
}
|
|
|
|
idMinigameInstance::~idMinigameInstance() {
|
|
Shutdown();
|
|
DeleteCriticalSection( &criticalSection );
|
|
}
|
|
|
|
void idMinigameInstance::Lock() {
|
|
EnterCriticalSection( &criticalSection );
|
|
}
|
|
|
|
void idMinigameInstance::Unlock() {
|
|
LeaveCriticalSection( &criticalSection );
|
|
}
|
|
|
|
void idMinigameInstance::PrintCallback( void *userData, const char *text ) {
|
|
if ( text && text[0] ) {
|
|
common->Printf( "minigame: %s", text );
|
|
}
|
|
}
|
|
|
|
void idMinigameInstance::SubmitAudioCallback( void *userData, const iceMinigameAudioBuffer_t *audio ) {
|
|
idMinigameInstance *self = reinterpret_cast<idMinigameInstance *>( userData );
|
|
if ( !self || !audio || !audio->samples || audio->frames <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
// The ABI already carries raw PCM across the DLL boundary. The engine mixer
|
|
// bridge will consume this callback once the minigame sound emitter lands.
|
|
}
|
|
|
|
bool idMinigameInstance::Start( const char *module, const char *image, const char *cmdLine ) {
|
|
Shutdown();
|
|
|
|
if ( !module || !module[0] || !image || !image[0] ) {
|
|
failed = true;
|
|
return false;
|
|
}
|
|
|
|
moduleName = module;
|
|
imageName = image;
|
|
commandLine = cmdLine ? cmdLine : "";
|
|
failed = false;
|
|
shutdownRequested = false;
|
|
|
|
idStr relativePath;
|
|
if ( moduleName.Find( "/" ) >= 0 || moduleName.Find( "\\" ) >= 0 ) {
|
|
relativePath = moduleName;
|
|
} else {
|
|
relativePath = "minigames/";
|
|
relativePath += moduleName;
|
|
}
|
|
if ( relativePath.Find( "." ) < 0 ) {
|
|
relativePath += ".minigame";
|
|
}
|
|
|
|
resolvedPath = fileSystem->RelativePathToOSPath( relativePath.c_str(), "fs_basepath" );
|
|
moduleHandle = Sys_DLL_Load( resolvedPath.c_str() );
|
|
if ( !moduleHandle ) {
|
|
common->Warning( "Could not load minigame '%s' from '%s'", moduleName.c_str(), resolvedPath.c_str() );
|
|
failed = true;
|
|
return false;
|
|
}
|
|
|
|
iceMinigameGetAPI_t GetMinigameAPI = (iceMinigameGetAPI_t)Sys_DLL_GetProcAddress( moduleHandle, ICE_MINIGAME_ENTRY_POINT );
|
|
if ( !GetMinigameAPI ) {
|
|
common->Warning( "Minigame '%s' does not export %s", moduleName.c_str(), ICE_MINIGAME_ENTRY_POINT );
|
|
Shutdown();
|
|
failed = true;
|
|
return false;
|
|
}
|
|
|
|
imports.version = ICE_MINIGAME_API_VERSION;
|
|
imports.userData = this;
|
|
importBasePath = fileSystem->RelativePathToOSPath( "", "fs_basepath" );
|
|
importSavePath = fileSystem->RelativePathToOSPath( "", "fs_savepath" );
|
|
imports.basePath = importBasePath.c_str();
|
|
imports.savePath = importSavePath.c_str();
|
|
imports.Print = PrintCallback;
|
|
imports.SubmitAudio = SubmitAudioCallback;
|
|
|
|
if ( !GetMinigameAPI( &imports, &exports ) || exports.version != ICE_MINIGAME_API_VERSION ||
|
|
!exports.CreateInstance || !exports.DestroyInstance || !exports.RunFrame || !exports.GetFramebuffer ) {
|
|
common->Warning( "Minigame '%s' has an incompatible API", moduleName.c_str() );
|
|
Shutdown();
|
|
failed = true;
|
|
return false;
|
|
}
|
|
|
|
if ( !exports.CreateInstance( &imports, commandLine.c_str(), &instance ) || !instance ) {
|
|
common->Warning( "Minigame '%s' failed to create an instance", moduleName.c_str() );
|
|
Shutdown();
|
|
failed = true;
|
|
return false;
|
|
}
|
|
|
|
if ( renderSystem && exports.framebufferWidth > 0 && exports.framebufferHeight > 0 ) {
|
|
renderSystem->CreateScratchImage( imageName.c_str(), exports.framebufferWidth, exports.framebufferHeight );
|
|
}
|
|
|
|
Sys_CreateThread( ThreadProc, this, THREAD_NORMAL, threadInfo, "minigame", g_threads, &g_thread_count );
|
|
threadStarted = true;
|
|
return true;
|
|
}
|
|
|
|
void idMinigameInstance::Shutdown() {
|
|
shutdownRequested = true;
|
|
if ( threadStarted && threadInfo.threadHandle ) {
|
|
Sys_DestroyThread( threadInfo );
|
|
}
|
|
threadStarted = false;
|
|
shutdownRequested = false;
|
|
|
|
if ( exports.DestroyInstance && instance ) {
|
|
exports.DestroyInstance( instance );
|
|
}
|
|
instance = NULL;
|
|
|
|
if ( moduleHandle ) {
|
|
Sys_DLL_Unload( moduleHandle );
|
|
}
|
|
moduleHandle = 0;
|
|
memset( &exports, 0, sizeof( exports ) );
|
|
|
|
Lock();
|
|
pendingEvents.Clear();
|
|
ClearFramebuffer();
|
|
frameReady = false;
|
|
active = false;
|
|
activeSent = false;
|
|
Unlock();
|
|
}
|
|
|
|
void idMinigameInstance::SetActive( bool setActive ) {
|
|
Lock();
|
|
active = setActive;
|
|
Unlock();
|
|
}
|
|
|
|
void idMinigameInstance::QueueEvent( const sysEvent_t *event, int time ) {
|
|
if ( !event || !instance ) {
|
|
return;
|
|
}
|
|
|
|
iceMinigameInputEvent_t minigameEvent;
|
|
minigameEvent.type = ICE_MINIGAME_EVENT_NONE;
|
|
minigameEvent.value = event->evValue;
|
|
minigameEvent.value2 = event->evValue2;
|
|
minigameEvent.time = time;
|
|
|
|
switch ( event->evType ) {
|
|
case SE_KEY:
|
|
minigameEvent.type = ICE_MINIGAME_EVENT_KEY;
|
|
break;
|
|
case SE_CHAR:
|
|
minigameEvent.type = ICE_MINIGAME_EVENT_CHAR;
|
|
break;
|
|
case SE_MOUSE:
|
|
minigameEvent.type = ICE_MINIGAME_EVENT_MOUSE;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
Lock();
|
|
if ( pendingEvents.Num() < 256 ) {
|
|
pendingEvents.Append( minigameEvent );
|
|
}
|
|
Unlock();
|
|
}
|
|
|
|
bool idMinigameInstance::UploadFramebuffer() {
|
|
if ( !renderSystem || !instance ) {
|
|
return false;
|
|
}
|
|
|
|
Lock();
|
|
if ( !frameReady || !framebuffer || framebufferWidth <= 0 || framebufferHeight <= 0 ) {
|
|
Unlock();
|
|
return false;
|
|
}
|
|
const bool uploaded = renderSystem->UploadImage( imageName.c_str(), framebuffer, framebufferWidth, framebufferHeight );
|
|
frameReady = false;
|
|
Unlock();
|
|
return uploaded;
|
|
}
|
|
|
|
unsigned int idMinigameInstance::ThreadProc( void *parm ) {
|
|
idMinigameInstance *self = reinterpret_cast<idMinigameInstance *>( parm );
|
|
return self ? self->RunThread() : 0;
|
|
}
|
|
|
|
unsigned int idMinigameInstance::RunThread() {
|
|
int lastTime = Sys_Milliseconds();
|
|
|
|
while ( !shutdownRequested ) {
|
|
iceMinigameInputEvent_t events[256];
|
|
int numEvents = 0;
|
|
bool nowActive;
|
|
bool sendActive;
|
|
|
|
Lock();
|
|
nowActive = active;
|
|
sendActive = ( activeSent != active );
|
|
activeSent = active;
|
|
numEvents = pendingEvents.Num();
|
|
if ( numEvents > 256 ) {
|
|
numEvents = 256;
|
|
}
|
|
for ( int i = 0; i < numEvents; i++ ) {
|
|
events[i] = pendingEvents[i];
|
|
}
|
|
pendingEvents.Clear();
|
|
Unlock();
|
|
|
|
if ( sendActive && exports.SetActive ) {
|
|
exports.SetActive( instance, nowActive ? 1 : 0 );
|
|
}
|
|
|
|
for ( int i = 0; i < numEvents; i++ ) {
|
|
if ( exports.QueueEvent ) {
|
|
exports.QueueEvent( instance, &events[i] );
|
|
}
|
|
}
|
|
|
|
const int now = Sys_Milliseconds();
|
|
const int elapsed = Max( 1, now - lastTime );
|
|
lastTime = now;
|
|
|
|
if ( nowActive || numEvents > 0 ) {
|
|
exports.RunFrame( instance, elapsed );
|
|
|
|
iceMinigameFramebuffer_t framebufferInfo;
|
|
memset( &framebufferInfo, 0, sizeof( framebufferInfo ) );
|
|
if ( exports.GetFramebuffer( instance, &framebufferInfo ) ) {
|
|
CopyFramebuffer( framebufferInfo );
|
|
}
|
|
}
|
|
|
|
Sys_Sleep( 1 );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void idMinigameInstance::CopyFramebuffer( const iceMinigameFramebuffer_t &source ) {
|
|
if ( !source.pixels || source.width <= 0 || source.height <= 0 || source.pitchBytes <= 0 ||
|
|
source.pixelFormat != ICE_MINIGAME_PIXEL_RGBA8 ) {
|
|
return;
|
|
}
|
|
|
|
const int rowBytes = source.width * 4;
|
|
const int requiredBytes = rowBytes * source.height;
|
|
|
|
Lock();
|
|
if ( requiredBytes != framebufferBytes ) {
|
|
ClearFramebuffer();
|
|
framebuffer = (byte *)Mem_Alloc( requiredBytes );
|
|
framebufferBytes = requiredBytes;
|
|
}
|
|
|
|
if ( framebuffer ) {
|
|
for ( int y = 0; y < source.height; y++ ) {
|
|
memcpy( framebuffer + y * rowBytes, source.pixels + y * source.pitchBytes, rowBytes );
|
|
}
|
|
framebufferWidth = source.width;
|
|
framebufferHeight = source.height;
|
|
frameReady = true;
|
|
}
|
|
Unlock();
|
|
}
|
|
|
|
void idMinigameInstance::ClearFramebuffer() {
|
|
if ( framebuffer ) {
|
|
Mem_Free( framebuffer );
|
|
}
|
|
framebuffer = NULL;
|
|
framebufferWidth = 0;
|
|
framebufferHeight = 0;
|
|
framebufferBytes = 0;
|
|
}
|