add a realtime scheduler class and priority-inheritance mutexes with timeouts and abandonment, add an HDA audio driver and a SYS_AUDIO_* syscall family, flesh out kernel32 with Win32 threads, events, mutexes, critical sections, and condition variables, add SYS_RESUME_THREAD for suspended thread creation, add KeInitializeMemoryTypes to reprogram MTRRs and install a canonical PAT for WB/WC/UC control, rework the display pipeline with a pitched present path and console output flush, bring back disk.vhd with mtools since vvfat fucking sucks, harden AHCI with serialized cache flush, USB mass storage with SCSI sense-data decoding, and xHCI with cleaner DMA teardown, fix the last stray for(;;) in HalStopProcessor to while(true), pile on smoke tests for audio, win32 sync primitives, and thread suspend, implement AHCI timeout, kill readahead since it was desperetaly trying to murder me, fix doom sound x2.

This commit is contained in:
maksw committed 2026-08-11 18:38:36 +02:00
1 parent 8b254f84f4
commit 4add6789ba
177 files changed
+22565 -3070

No files matched your search

+159 -115
View File
@@ -7549,15 +7549,25 @@ void doom_init(int argc, const char** argv, int flags)
if (!doom_exit) doom_exit = doom_exit_impl;
if (!doom_getenv) doom_getenv = doom_getenv_impl;
doom_print("doom_init: allocating screen buffer\n");
screen_buffer = (unsigned char*)doom_malloc(SCREENWIDTH * SCREENHEIGHT);
doom_print("doom_init: screen buffer ready\n");
doom_print("doom_init: allocating final screen buffer\n");
final_screen_buffer = (unsigned char*)doom_malloc(SCREENWIDTH * SCREENHEIGHT * 4);
doom_print("doom_init: final screen buffer ready\n");
doom_print("doom_init: reading initial time\n");
last_update_time = I_GetTime();
doom_print("doom_init: initial time ready\n");
myargc = argc;
myargv = (char**)argv; // unsafe my ass
doom_flags = flags;
doom_print("doom_init: entering D_DoomMain\n");
D_DoomMain();
doom_print("doom_init: D_DoomMain returned\n");
}
@@ -9790,7 +9800,27 @@ void FindResponseFile(void)
{
int i;
doom_print("FindResponseFile: entered\n");
if (myargc <= 0)
{
doom_print("FindResponseFile: argc <= 0\n");
return;
}
if (myargc == 1)
{
doom_print("FindResponseFile: argc == 1, returning\n");
return;
}
doom_print("FindResponseFile: argc > 1\n");
for (i = 1; i < myargc; i++)
{
doom_print("FindResponseFile: checking argument\n");
if (!myargv || !myargv[i])
{
doom_print("FindResponseFile: invalid argv entry\n");
return;
}
if (myargv[i][0] == '@')
{
void* handle = 0;
@@ -9860,6 +9890,9 @@ void FindResponseFile(void)
break;
}
}
doom_print("FindResponseFile: scan complete\n");
}
@@ -9871,9 +9904,13 @@ void D_DoomMain(void)
int p;
char file[256];
doom_print("D_DoomMain: FindResponseFile\n");
FindResponseFile();
doom_print("D_DoomMain: FindResponseFile done\n");
doom_print("D_DoomMain: IdentifyVersion\n");
IdentifyVersion();
doom_print("D_DoomMain: IdentifyVersion done\n");
modifiedgame = doom_false;
@@ -23741,19 +23778,67 @@ void M_SaveDefaults(void)
//
// M_LoadDefaults
//
// Reads one complete defaults line without allowing malformed files to escape the caller's buffer.
static int M_ReadDefaultsLine(void* handle, char* line, int capacity)
{
int length = 0;
doom_boolean overflow = doom_false;
if (!handle || !line || capacity < 2)
return 0;
for (;;)
{
char c = 0;
int read = doom_read(handle, &c, 1);
if (read != 1)
{
if (!length && !overflow)
return 0;
break;
}
if (c == '\n')
break;
if (c == '\r')
continue;
if (length + 1 < capacity)
line[length++] = c;
else
overflow = doom_true;
}
line[length] = 0;
return overflow ? -1 : 1;
}
// Returns the first non-whitespace byte in a mutable defaults token.
static char* M_SkipDefaultWhitespace(char* text)
{
while (*text == ' ' || *text == '\t')
text++;
return text;
}
// Removes horizontal whitespace from the end of a mutable defaults token.
static void M_TrimDefaultWhitespace(char* text)
{
int length = (int)doom_strlen(text);
while (length > 0 && (text[length - 1] == ' ' || text[length - 1] == '\t'))
text[--length] = 0;
}
// Loads bounded name/value records from the optional defaults file.
void M_LoadDefaults(void)
{
char line[384];
int i;
int len;
void* f;
char def[80];
char strparm[100];
char* newstring;
int parm;
doom_boolean isstring;
// set everything to base values
// numdefaults = sizeof(defaults)/sizeof(defaults[0]);
// Set everything to base values.
for (i = 0; i < numdefaults; i++)
{
if (defaults[i].defaultvalue == 0xFFFF)
@@ -23762,104 +23847,80 @@ void M_LoadDefaults(void)
*defaults[i].location = (int)defaults[i].defaultvalue;
}
// check for a custom default file
// Check for a custom default file.
i = M_CheckParm((char*)"-config");
if (i && i < myargc - 1)
{
defaultfile = myargv[i + 1];
//doom_print(" default file: %s\n", defaultfile);
doom_print(" default file: ");
doom_print(defaultfile);
doom_print("\n");
}
else
defaultfile = basedefault;
// read the file in, overriding any set defaults
f = doom_open(defaultfile, "r");
if (f)
{
while (!doom_eof(f))
defaultfile = basedefault;
}
void* f = doom_open(defaultfile, "r");
if (!f)
return;
for (;;)
{
int lineStatus = M_ReadDefaultsLine(f, line, (int)sizeof(line));
if (!lineStatus)
break;
if (lineStatus < 0)
{
// def
int arg_read = 0;
char c;
for (i = 0; i < 79; ++i)
{
doom_read(f, &c, 1);
if (c == ' ' || c == '\n' || c == '\t')
{
if (i > 0) arg_read++;
break;
}
def[i] = c;
}
def[i] = '\0';
// Ignore spaces
if (c != '\n')
{
while (1)
{
doom_read(f, &c, 1);
if (c != ' ' && c != '\t') break;
}
// strparam
i = 0;
if (c != '\n')
{
for (; i < 260;)
{
strparm[i++] = c;
doom_read(f, &c, 1);
if (c == '\n')
{
if (i > 0) arg_read++;
break;
}
}
}
strparm[i] = '\0';
}
isstring = doom_false;
//if (fscanf(f, "%79s %[^\n]\n", def, strparm) == 2)
if (arg_read == 2)
{
if (strparm[0] == '"')
{
// get a string default
isstring = doom_true;
len = (int)doom_strlen(strparm);
newstring = (char*)doom_malloc(len);
strparm[len - 1] = 0;
doom_strcpy(newstring, strparm + 1);
}
else if (strparm[0] == '0' && strparm[1] == 'x')
{
//sscanf(strparm + 2, "%x", &parm);
parm = doom_atox(strparm + 2);
}
else
{
//sscanf(strparm, "%i", &parm);
parm = doom_atoi(strparm);
}
for (i = 0; i < numdefaults; i++)
if (!doom_strcmp(def, defaults[i].name))
{
if (!isstring)
*defaults[i].location = parm;
else
*defaults[i].text_location = newstring;
break;
}
}
doom_print("M_LoadDefaults: ignored an overlong defaults line.\n");
continue;
}
doom_close(f);
char* name = M_SkipDefaultWhitespace(line);
if (!*name || *name == '#')
continue;
char* value = name;
while (*value && *value != ' ' && *value != '\t')
value++;
if (!*value)
continue;
*value++ = 0;
value = M_SkipDefaultWhitespace(value);
M_TrimDefaultWhitespace(value);
if (!*value)
continue;
default_t* setting = get_default(name);
if (!setting)
continue;
if (value[0] == '"')
{
int length = (int)doom_strlen(value);
if (length < 2 || value[length - 1] != '"' || !setting->text_location)
continue;
int textLength = length - 2;
char* newstring = (char*)doom_malloc(textLength + 1);
if (!newstring)
continue;
doom_memcpy(newstring, value + 1, textLength);
newstring[textLength] = 0;
*setting->text_location = newstring;
}
else if (setting->location)
{
int parsed;
if (value[0] == '0' && value[1] == 'x')
parsed = doom_atox(value + 2);
else
parsed = doom_atoi(value);
*setting->location = parsed;
}
}
doom_close(f);
}
@@ -37068,10 +37129,6 @@ void R_InitTextures(void)
int* directory;
int temp1;
int temp2;
int temp3;
// Load the patch names from pnames.lmp.
name[8] = 0;
names = (char*)W_CacheLumpName((char*)"PNAMES", PU_STATIC);
@@ -37118,23 +37175,10 @@ void R_InitTextures(void)
totalwidth = 0;
// Really complex printing shit...
temp1 = W_GetNumForName((char*)"S_START"); // P_???????
temp2 = W_GetNumForName((char*)"S_END") - 1;
temp3 = ((temp2 - temp1 + 63) / 64) + ((numtextures + 63) / 64);
doom_print("[");
for (i = 0; i < temp3; i++)
doom_print(" ");
doom_print(" ]");
for (i = 0; i < temp3; i++)
doom_print("\x8");
doom_print("\x8\x8\x8\x8\x8\x8\x8\x8\x8\x8");
// Cursor-control progress output can block simple platform consoles.
for (i = 0; i < numtextures; i++, directory++)
{
if (!(i & 63))
doom_print(".");
if (i == numtextures1)
{
// Start looking in second texture file.
@@ -37285,14 +37329,14 @@ void R_InitColormaps(void)
//
void R_InitData(void)
{
R_InitTextures();
doom_print("\nInitTextures");
R_InitFlats();
R_InitTextures();
doom_print("\nInitFlats");
R_InitSpriteLumps();
R_InitFlats();
doom_print("\nInitSprites");
R_InitColormaps();
R_InitSpriteLumps();
doom_print("\nInitColormaps");
R_InitColormaps();
}
+18 -3
View File
@@ -1,6 +1,6 @@
// doom.c — PureDOOM port
// Native personality carrying all the weight.
// Drop doom1.wad (or doom.wad) next to this binary on your FAT32 volume.
// Place the Doom WAD and optional doom.sf2 in the process working directory.
#include <stdint.h>
#include <stddef.h>
@@ -10,6 +10,8 @@
#include <intrin.h>
#include <ntlite/syscall.h>
#include "doom_audio.h"
#define DOOM_IMPLEMENTATION
#include "PureDOOM.h"
@@ -189,6 +191,7 @@ static void doom_gettime_wrapper(int* sec, int* usec)
// ---------------------------------------------------------------------------
static void doom_exit_wrapper(int code)
{
DoomAudioAbort();
SysExitProcess(code);
}
@@ -445,7 +448,7 @@ static int doom_now_tic(void)
// Waits until Doom has a new simulation tic to render.
static int wait_for_next_doom_tic(int lastTic)
{
for (;;) {
while (true) {
int now = doom_now_tic();
if (now != lastTic)
return now;
@@ -495,9 +498,18 @@ int main(void)
char argvStorage[512];
const char* argv[32];
int argc = build_argv_from_command_line(cmdline, argvStorage, sizeof(argvStorage), argv, 32);
if (argc == 1)
doom_print_wrapper("doom: parsed argc == 1\n");
else if (argc > 1)
doom_print_wrapper("doom: parsed argc > 1\n");
else
doom_print_wrapper("doom: parsed argc <= 0\n");
init_surface();
init_display_clock();
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting doom\n") }; SysWriteConsole(&req); } while (0);
doom_init(argc, argv, 0);
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting audio\n") }; SysWriteConsole(&req); } while (0);
DoomAudioStart();
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: entering game loop\n") }; SysWriteConsole(&req); } while (0);
@@ -508,9 +520,12 @@ int main(void)
int frameTic = wait_for_next_doom_tic(lastFrameTic);
lastFrameTic = frameTic;
DoomAudioLockCore();
poll_input();
doom_update();
render_frame((const uint8_t*)doom_get_framebuffer(4 /* RGBA */));
const uint8_t* framebuffer = (const uint8_t*)doom_get_framebuffer(4 /* RGBA */);
DoomAudioUnlockCore();
render_frame(framebuffer);
}
SysExitProcess(0);
+802
View File
@@ -0,0 +1,802 @@
// Doom audio mixes PureDOOM SFX with optional SoundFont MIDI into one native render stream.
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <intrin.h>
#include <ntlite/syscall.h>
#include "doom_audio.h"
#include "PureDOOM.h"
#define DOOM_AUDIO_SAMPLE_RATE DOOM_SAMPLERATE
#define DOOM_AUDIO_CHANNELS 2u
#define DOOM_AUDIO_BLOCK_FRAMES 512u
#define DOOM_AUDIO_WRITE_QUANTUM_FRAMES 256u
#define DOOM_AUDIO_PREFILL_FRAMES DOOM_AUDIO_BLOCK_FRAMES
#define DOOM_AUDIO_REFILL_WATERMARK_FRAMES DOOM_AUDIO_WRITE_QUANTUM_FRAMES
#define DOOM_AUDIO_MIDI_BATCH_MESSAGES 256u
#define DOOM_AUDIO_SOUNDFONT_PATH "doom.sf2"
#define DOOM_AUDIO_MAX_SOUNDFONT_BYTES (64u * 1024u * 1024u)
#define DOOM_AUDIO_WAIT_MILLISECONDS 100u
#define DOOM_AUDIO_WORKER_PRIORITY 2u
#define DOOM_AUDIO_SYNTH_LOADER_PRIORITY 0u
#define DOOM_AUDIO_MAX_VOICES 128
#define DOOM_AUDIO_MUSIC_GAIN 1.0f
// Computes a natural logarithm for the positive finite values used by TinySoundFont.
static double DoomAudioLog(double value) {
union {
double Value;
uint64_t Bits;
} bits;
const double ln2 = 0.69314718055994530942;
int exponent;
double mantissa;
double ratio;
double ratio2;
double term;
double sum;
if (value <= 0.0)
return -1.0e300;
bits.Value = value;
exponent = (int)((bits.Bits >> 52) & 0x7FFu) - 1023;
bits.Bits = (bits.Bits & UINT64_C(0x000FFFFFFFFFFFFF)) |
UINT64_C(0x3FF0000000000000);
mantissa = bits.Value;
ratio = (mantissa - 1.0) / (mantissa + 1.0);
ratio2 = ratio * ratio;
term = ratio;
sum = term;
term *= ratio2;
sum += term / 3.0;
term *= ratio2;
sum += term / 5.0;
term *= ratio2;
sum += term / 7.0;
term *= ratio2;
sum += term / 9.0;
term *= ratio2;
sum += term / 11.0;
term *= ratio2;
sum += term / 13.0;
return (double)exponent * ln2 + 2.0 * sum;
}
// Scales a normal double by an integral power of two without a runtime math library.
static double DoomAudioScalePowerOfTwo(double value, int exponent) {
union {
double Value;
uint64_t Bits;
} scale;
if (exponent > 1023)
return 1.0e300;
if (exponent < -1022)
return 0.0;
scale.Bits = (uint64_t)(exponent + 1023) << 52;
return value * scale.Value;
}
// Computes an exponential over the bounded ranges used by TinySoundFont envelopes.
static double DoomAudioExp(double value) {
const double ln2 = 0.69314718055994530942;
const double inverseLn2 = 1.4426950408889634074;
int exponent;
double reduced;
double result;
if (value >= 700.0)
return 1.0e300;
if (value <= -700.0)
return 0.0;
exponent = (int)(value * inverseLn2 + (value >= 0.0 ? 0.5 : -0.5));
reduced = value - (double)exponent * ln2;
result = 1.0 + reduced *
(1.0 + reduced *
(0.5 + reduced *
(0.16666666666666666667 + reduced *
(0.04166666666666666667 + reduced *
(0.00833333333333333333 + reduced *
(0.00138888888888888889 + reduced *
0.00019841269841269841))))));
return DoomAudioScalePowerOfTwo(result, exponent);
}
// Computes a positive real power for TinySoundFont frequency and gain conversion.
static double DoomAudioPow(double base, double exponent) {
if (base <= 0.0)
return 0.0;
return DoomAudioExp(exponent * DoomAudioLog(base));
}
// Computes the float exponential required by TinySoundFont envelopes.
static float DoomAudioExpf(float value) {
return (float)DoomAudioExp((double)value);
}
// Computes the float power required by TinySoundFont frequency and gain conversion.
static float DoomAudioPowf(float base, float exponent) {
return (float)DoomAudioPow((double)base, (double)exponent);
}
// Computes base-ten logarithms from the shared natural logarithm approximation.
static double DoomAudioLog10(double value) {
return DoomAudioLog(value) * 0.43429448190325182765;
}
// Computes square roots with a bit-derived seed and stable Newton refinement.
static double DoomAudioSqrt(double value) {
union {
double Value;
uint64_t Bits;
} estimate;
if (value <= 0.0)
return 0.0;
estimate.Value = value;
estimate.Bits = (estimate.Bits >> 1) + UINT64_C(0x1FF8000000000000);
for (int iteration = 0; iteration < 6; iteration++)
estimate.Value = 0.5 * (estimate.Value + value / estimate.Value);
return estimate.Value;
}
// Computes the float square root required by TinySoundFont panning.
static float DoomAudioSqrtf(float value) {
return (float)DoomAudioSqrt((double)value);
}
// Computes tangent accurately over TinySoundFont's normalized low-pass range.
static double DoomAudioTan(double value) {
double squared = value * value;
double sine = value *
(1.0 + squared *
(-0.16666666666666666667 + squared *
(0.00833333333333333333 + squared *
(-0.00019841269841269841 + squared *
(0.00000275573192239859 + squared *
(-0.00000002505210838544 + squared *
0.00000000016059043837))))));
double cosine = 1.0 + squared *
(-0.5 + squared *
(0.04166666666666666667 + squared *
(-0.00138888888888888889 + squared *
(0.00002480158730158730 + squared *
(-0.00000027557319223986 + squared *
(0.00000000208767569879 + squared *
-0.00000000001147074560))))));
if (cosine > -1.0e-12 && cosine < 1.0e-12)
cosine = cosine < 0.0 ? -1.0e-12 : 1.0e-12;
return sine / cosine;
}
#pragma warning(push)
#pragma warning(disable: 4244 4267 4706)
#define TSF_NO_STDIO
#define TSF_MALLOC malloc
#define TSF_REALLOC realloc
#define TSF_FREE free
#define TSF_MEMCPY memcpy
#define TSF_MEMSET memset
#define TSF_POW DoomAudioPow
#define TSF_POWF DoomAudioPowf
#define TSF_EXPF DoomAudioExpf
#define TSF_LOG DoomAudioLog
#define TSF_TAN DoomAudioTan
#define TSF_LOG10 DoomAudioLog10
#define TSF_SQRT DoomAudioSqrt
#define TSF_SQRTF DoomAudioSqrtf
#define TSF_IMPLEMENTATION
#include "third_party/tsf.h"
#pragma warning(pop)
typedef struct DOOM_AUDIO_STATE {
uint32_t CoreMutex;
uint32_t StreamHandle;
uint32_t WorkerHandle;
uint32_t WorkerId;
uint32_t SynthLoaderHandle;
uint32_t SynthLoaderId;
volatile long StopRequested;
tsf* volatile Synth;
} DOOM_AUDIO_STATE;
static DOOM_AUDIO_STATE g_doomAudio;
// Writes one complete diagnostic line to the process console.
static void DoomAudioPrint(const char* text) {
SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) };
(void)SysWriteConsole(&request);
}
// Opens the default available native render endpoint.
static KSTATUS DoomAudioFindEndpoint(uint64_t* endpointIdOut) {
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
uint32_t count = 0;
const SYS_AUDIO_ENDPOINT_INFO* fallback = NULL;
KSTATUS status;
if (!endpointIdOut)
return NTLSTATUS_INVALID_PARAMETER;
*endpointIdOut = 0;
memset(endpoints, 0, sizeof(endpoints));
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Endpoints = endpoints;
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
request.CountOut = &count;
status = SysAudioEnumerateEndpoints(&request);
if (status != NTLSTATUS_SUCCESS)
return status;
if (count > SYS_AUDIO_MAX_ENDPOINTS)
count = SYS_AUDIO_MAX_ENDPOINTS;
for (uint32_t index = 0; index < count; index++) {
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
if (endpoint->State != SysAudioEndpointReady ||
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
continue;
if (!fallback)
fallback = endpoint;
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT) {
*endpointIdOut = endpoint->EndpointId;
return NTLSTATUS_SUCCESS;
}
}
if (!fallback)
return NTLSTATUS_NOT_FOUND;
*endpointIdOut = fallback->EndpointId;
return NTLSTATUS_SUCCESS;
}
// Returns the currently published synthesizer without taking the PureDOOM lock.
static tsf* DoomAudioGetSynth(void) {
return (tsf*)_InterlockedCompareExchangePointer(
(void* volatile*)&g_doomAudio.Synth, NULL, NULL);
}
// Maps the optional SoundFont so TinySoundFont can parse it without a separate input copy.
static KSTATUS DoomAudioMapSoundFont(void** dataOut, uint32_t* sizeOut) {
uint32_t fileHandle = 0;
uint32_t mappingHandle = 0;
uint64_t fileSize = 0;
void* base = NULL;
KSTATUS status;
if (!dataOut || !sizeOut)
return NTLSTATUS_INVALID_PARAMETER;
*dataOut = NULL;
*sizeOut = 0;
SYS_OPEN_FILE_REQUEST open = SysMakeOpenFileRequest(
SysSvFromCstr(DOOM_AUDIO_SOUNDFONT_PATH),
SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_READ,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&fileHandle);
status = SysOpenFile(&open);
if (status != NTLSTATUS_SUCCESS)
return status;
status = SysGetFileSize(fileHandle, &fileSize);
if (status != NTLSTATUS_SUCCESS || fileSize == 0 ||
fileSize > DOOM_AUDIO_MAX_SOUNDFONT_BYTES ||
fileSize > INT32_MAX) {
(void)SysCloseHandle(fileHandle);
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_INVALID_PARAMETER : status;
}
status = SysCreateFileMapping(fileHandle, SYS_VM_READ, SYS_MAP_FLAG_NONE,
fileSize, &mappingHandle);
if (status != NTLSTATUS_SUCCESS || !mappingHandle) {
(void)SysCloseHandle(fileHandle);
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status;
}
status = SysMapFile(mappingHandle, 0, (size_t)fileSize,
SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
(void)SysCloseHandle(mappingHandle);
(void)SysCloseHandle(fileHandle);
if (status != NTLSTATUS_SUCCESS || !base)
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status;
*dataOut = base;
*sizeOut = (uint32_t)fileSize;
return NTLSTATUS_SUCCESS;
}
// Configures one parsed General MIDI synthesizer for PureDOOM's native sample rate.
static KSTATUS DoomAudioConfigureSynth(tsf* synth) {
if (!synth)
return NTLSTATUS_INVALID_PARAMETER;
tsf_set_output(synth, TSF_STEREO_INTERLEAVED,
(int)DOOM_AUDIO_SAMPLE_RATE, 0.0f);
if (!tsf_set_max_voices(synth, DOOM_AUDIO_MAX_VOICES))
return NTLSTATUS_NO_MEMORY;
for (int channel = 0; channel < 16; channel++) {
if (!tsf_channel_set_presetnumber(synth, channel, 0, channel == 9))
return NTLSTATUS_FAILURE;
}
return NTLSTATUS_SUCCESS;
}
// Loads optional MIDI state at low priority without blocking Doom or SFX startup.
static void DoomAudioSynthLoader(uintptr_t argument) {
uint32_t soundFontSize = 0;
void* soundFontBase = NULL;
tsf* synth = NULL;
KSTATUS status;
(void)argument;
status = DoomAudioMapSoundFont(&soundFontBase, &soundFontSize);
if (status == NTLSTATUS_NOT_FOUND) {
DoomAudioPrint("doom: doom.sf2 unavailable; continuing with sound effects only\n");
SysExitThread(0);
}
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: doom.sf2 could not be mapped; continuing with sound effects only\n");
SysExitThread(0);
}
if (_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0)) {
(void)SysFreeVm(soundFontBase);
SysExitThread(0);
}
synth = tsf_load_memory(soundFontBase, (int)soundFontSize);
(void)SysFreeVm(soundFontBase);
if (!synth) {
DoomAudioPrint("doom: doom.sf2 is invalid or synth allocation failed; continuing with sound effects only\n");
SysExitThread(0);
}
status = DoomAudioConfigureSynth(synth);
if (status != NTLSTATUS_SUCCESS ||
_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0)) {
if (status != NTLSTATUS_SUCCESS)
DoomAudioPrint("doom: MIDI synthesizer initialization failed; continuing with sound effects only\n");
tsf_close(synth);
SysExitThread(0);
}
(void)_InterlockedExchangePointer(
(void* volatile*)&g_doomAudio.Synth, synth);
DoomAudioPrint("doom: MIDI synthesizer ready\n");
SysExitThread(0);
}
// Applies one packed WinMM-style short MIDI message to TinySoundFont.
static void DoomAudioDispatchMidi(tsf* synth, uint32_t message) {
uint8_t status = (uint8_t)(message & 0xFFu);
uint8_t data1 = (uint8_t)((message >> 8) & 0x7Fu);
uint8_t data2 = (uint8_t)((message >> 16) & 0x7Fu);
int channel = status & 0x0F;
if (!synth)
return;
switch (status & 0xF0) {
case 0x80:
tsf_channel_note_off(synth, channel, data1);
break;
case 0x90:
if (data2)
(void)tsf_channel_note_on(synth, channel, data1,
(float)data2 / 127.0f);
else
tsf_channel_note_off(synth, channel, data1);
break;
case 0xB0:
(void)tsf_channel_midi_control(synth, channel, data1, data2);
break;
case 0xC0:
(void)tsf_channel_set_presetnumber(synth, channel, data1,
channel == 9);
break;
case 0xE0:
(void)tsf_channel_set_pitchwheel(synth, channel,
(int)data1 | ((int)data2 << 7));
break;
default:
break;
}
}
// Advances one PureDOOM MIDI tick and dispatches every event outside the core lock.
static void DoomAudioTickMidi(tsf* synth) {
uint32_t messages[DOOM_AUDIO_MIDI_BATCH_MESSAGES];
int tickComplete = 0;
if (!synth)
return;
while (!tickComplete) {
uint32_t count = 0;
DoomAudioLockCore();
while (count < DOOM_AUDIO_MIDI_BATCH_MESSAGES) {
uint32_t message = (uint32_t)doom_tick_midi();
if (!message) {
tickComplete = 1;
break;
}
messages[count++] = message;
}
DoomAudioUnlockCore();
for (uint32_t index = 0; index < count; index++)
DoomAudioDispatchMidi(synth, messages[index]);
}
}
// Copies one complete PureDOOM SFX block while holding the shared core lock.
static void DoomAudioReadSfxBlock(int16_t* frames) {
short* source;
DoomAudioLockCore();
source = doom_get_sound_buffer();
memcpy(frames, source,
DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS * sizeof(int16_t));
DoomAudioUnlockCore();
}
// Mixes rendered floating-point music into signed 16-bit PureDOOM SFX.
static void DoomAudioMixMusic(int16_t* destination,
const float* music,
uint32_t frameOffset,
uint32_t frameCount) {
uint32_t firstSample = frameOffset * DOOM_AUDIO_CHANNELS;
uint32_t sampleCount = frameCount * DOOM_AUDIO_CHANNELS;
for (uint32_t index = 0; index < sampleCount; index++) {
float scaled = music[index] * (32767.0f * DOOM_AUDIO_MUSIC_GAIN);
int32_t synthSample;
int32_t mixed;
if (scaled > 32767.0f)
synthSample = 32767;
else if (scaled < -32768.0f)
synthSample = -32768;
else
synthSample = (int32_t)scaled;
mixed = (int32_t)destination[firstSample + index] + synthSample;
if (mixed > 32767)
mixed = 32767;
else if (mixed < -32768)
mixed = -32768;
destination[firstSample + index] = (int16_t)mixed;
}
}
// Renders MIDI at exactly 140 ticks per second into one native PureDOOM block.
static void DoomAudioRenderMusic(tsf* synth,
int16_t* frames,
uint32_t* phaseInOut) {
float music[DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS];
uint32_t frameOffset = 0;
memset(music, 0, sizeof(music));
while (frameOffset < DOOM_AUDIO_BLOCK_FRAMES) {
uint32_t remaining;
uint32_t framesUntilTick;
while (*phaseInOut >= DOOM_AUDIO_SAMPLE_RATE) {
*phaseInOut -= DOOM_AUDIO_SAMPLE_RATE;
DoomAudioTickMidi(synth);
}
remaining = DOOM_AUDIO_BLOCK_FRAMES - frameOffset;
framesUntilTick =
(DOOM_AUDIO_SAMPLE_RATE - *phaseInOut + DOOM_MIDI_RATE - 1u) /
DOOM_MIDI_RATE;
if (framesUntilTick > remaining)
framesUntilTick = remaining;
tsf_render_float(synth,
music + frameOffset * DOOM_AUDIO_CHANNELS,
(int)framesUntilTick,
0);
DoomAudioMixMusic(frames, music + frameOffset * DOOM_AUDIO_CHANNELS,
frameOffset, framesUntilTick);
frameOffset += framesUntilTick;
*phaseInOut += framesUntilTick * DOOM_MIDI_RATE;
}
}
// Renders one complete native-rate block containing synchronized music and SFX.
static void DoomAudioRenderBlock(int16_t* frames,
tsf** activeSynthInOut,
uint32_t* midiPhaseInOut) {
tsf* synth;
DoomAudioReadSfxBlock(frames);
synth = DoomAudioGetSynth();
if (synth != *activeSynthInOut) {
*activeSynthInOut = synth;
*midiPhaseInOut = DOOM_AUDIO_SAMPLE_RATE;
}
if (synth)
DoomAudioRenderMusic(synth, frames, midiPhaseInOut);
}
// Queries one coherent native stream snapshot.
static KSTATUS DoomAudioQueryStream(SYS_AUDIO_STREAM_INFO* infoOut) {
if (!infoOut)
return NTLSTATUS_INVALID_PARAMETER;
memset(infoOut, 0, sizeof(*infoOut));
infoOut->Size = sizeof(*infoOut);
return SysAudioQueryStream(g_doomAudio.StreamHandle, infoOut);
}
// Arms a wait for writable space or a terminal stream condition.
static KSTATUS DoomAudioWaitWritable(const SYS_AUDIO_STREAM_INFO* info,
uint32_t writableFrames) {
SYS_AUDIO_ARM_WAIT_REQUEST request;
KSTATUS status;
if (!info || !writableFrames || writableFrames > info->CapacityFrames)
return NTLSTATUS_INVALID_PARAMETER;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.StreamHandle = g_doomAudio.StreamHandle;
request.Mask = SYS_AUDIO_WAIT_WRITABLE |
SYS_AUDIO_WAIT_FAILED |
SYS_AUDIO_WAIT_DISCONTINUITY;
request.WritableFrames = writableFrames;
request.DiscontinuityGeneration = info->DiscontinuityGeneration;
status = SysAudioArmWait(&request);
if (status != NTLSTATUS_SUCCESS)
return status;
return SysWaitSingle(g_doomAudio.StreamHandle,
SYS_WAIT_FLAG_NONE,
DOOM_AUDIO_WAIT_MILLISECONDS);
}
// Waits until the buffered source has fallen to the low-latency refill watermark.
static KSTATUS DoomAudioWaitForRefill(const SYS_AUDIO_STREAM_INFO* info) {
if (!info || info->CapacityFrames <= DOOM_AUDIO_REFILL_WATERMARK_FRAMES)
return NTLSTATUS_INVALID_PARAMETER;
return DoomAudioWaitWritable(
info, info->CapacityFrames - DOOM_AUDIO_REFILL_WATERMARK_FRAMES);
}
// Writes as much of one pending block as the nonblocking stream accepts.
static KSTATUS DoomAudioWritePending(const int16_t* frames,
uint32_t* frameOffsetInOut) {
SYS_AUDIO_WRITE_REQUEST request;
uint32_t written = 0;
KSTATUS status;
if (!frames || !frameOffsetInOut ||
*frameOffsetInOut >= DOOM_AUDIO_BLOCK_FRAMES)
return NTLSTATUS_INVALID_PARAMETER;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.StreamHandle = g_doomAudio.StreamHandle;
request.FrameCount = DOOM_AUDIO_BLOCK_FRAMES - *frameOffsetInOut;
if (request.FrameCount > DOOM_AUDIO_WRITE_QUANTUM_FRAMES)
request.FrameCount = DOOM_AUDIO_WRITE_QUANTUM_FRAMES;
request.Frames = frames + *frameOffsetInOut * DOOM_AUDIO_CHANNELS;
request.WrittenFramesOut = &written;
status = SysAudioWrite(&request);
if (status != NTLSTATUS_SUCCESS)
return status;
if (!written)
return NTLSTATUS_WOULD_BLOCK;
*frameOffsetInOut += written;
return NTLSTATUS_SUCCESS;
}
// Keeps a bounded low-latency stream queue at PureDOOM's native cadence.
static void DoomAudioWorker(uintptr_t argument) {
int16_t block[DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS];
uint32_t pendingOffset = DOOM_AUDIO_BLOCK_FRAMES;
uint32_t midiPhase = 0;
tsf* activeSynth = NULL;
uint8_t started = 0;
(void)argument;
while (true) {
SYS_AUDIO_STREAM_INFO info;
uint32_t requiredFrames;
KSTATUS status;
if (_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0))
break;
status = DoomAudioQueryStream(&info);
if (status != NTLSTATUS_SUCCESS || info.State == SysAudioStreamFailed) {
DoomAudioPrint("doom: native audio stream failed\n");
break;
}
if (!started && info.BufferedFrames >= DOOM_AUDIO_PREFILL_FRAMES) {
status = SysAudioStart(g_doomAudio.StreamHandle);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: native audio start failed\n");
break;
}
started = 1;
continue;
}
if (pendingOffset == DOOM_AUDIO_BLOCK_FRAMES) {
if (started &&
info.BufferedFrames > DOOM_AUDIO_REFILL_WATERMARK_FRAMES) {
status = DoomAudioWaitForRefill(&info);
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_TIMEOUT) {
DoomAudioPrint("doom: native audio wait failed\n");
break;
}
continue;
}
DoomAudioRenderBlock(block, &activeSynth, &midiPhase);
pendingOffset = 0;
}
requiredFrames = DOOM_AUDIO_BLOCK_FRAMES - pendingOffset;
if (requiredFrames > DOOM_AUDIO_WRITE_QUANTUM_FRAMES)
requiredFrames = DOOM_AUDIO_WRITE_QUANTUM_FRAMES;
if (info.WritableFrames < requiredFrames) {
status = DoomAudioWaitWritable(&info, requiredFrames);
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_TIMEOUT) {
DoomAudioPrint("doom: native audio wait failed\n");
break;
}
continue;
}
status = DoomAudioWritePending(block, &pendingOffset);
if (status == NTLSTATUS_WOULD_BLOCK)
continue;
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: native audio write failed\n");
break;
}
}
if (started)
(void)SysAudioStop(g_doomAudio.StreamHandle);
SysExitThread(0);
}
// Releases resources owned before or after the producer thread lifetime.
static void DoomAudioReleaseResources(void) {
if (g_doomAudio.StreamHandle) {
(void)SysCloseHandle(g_doomAudio.StreamHandle);
g_doomAudio.StreamHandle = 0;
}
tsf* synth = (tsf*)_InterlockedExchangePointer(
(void* volatile*)&g_doomAudio.Synth, NULL);
if (synth)
tsf_close(synth);
if (g_doomAudio.CoreMutex) {
(void)SysCloseHandle(g_doomAudio.CoreMutex);
g_doomAudio.CoreMutex = 0;
}
}
// Starts one process-owned audio stream and its exclusive producer thread.
void DoomAudioStart(void) {
uint64_t endpointId = 0;
uint32_t streamHandle = 0;
KSTATUS status;
if (DOOM_AUDIO_PREFILL_FRAMES >= SYS_AUDIO_STREAM_CAPACITY_FRAMES ||
DOOM_AUDIO_REFILL_WATERMARK_FRAMES >= DOOM_AUDIO_PREFILL_FRAMES ||
DOOM_AUDIO_WRITE_QUANTUM_FRAMES > DOOM_AUDIO_BLOCK_FRAMES) {
DoomAudioPrint("doom: invalid native audio buffering configuration\n");
return;
}
memset(&g_doomAudio, 0, sizeof(g_doomAudio));
status = SysCreateMutex(&g_doomAudio.CoreMutex);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: audio synchronization unavailable; continuing without sound\n");
return;
}
status = DoomAudioFindEndpoint(&endpointId);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: no audio endpoint; continuing without sound\n");
DoomAudioReleaseResources();
return;
}
SYS_AUDIO_OPEN_STREAM_REQUEST open;
memset(&open, 0, sizeof(open));
open.Size = sizeof(open);
open.EndpointId = endpointId;
open.Format.SampleRate = DOOM_AUDIO_SAMPLE_RATE;
open.Format.ChannelCount = DOOM_AUDIO_CHANNELS;
open.Format.SampleFormat = SysAudioSampleS16;
open.StreamHandleOut = &streamHandle;
status = SysAudioOpenStream(&open);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: native audio open failed; continuing without sound\n");
DoomAudioReleaseResources();
return;
}
g_doomAudio.StreamHandle = streamHandle;
SYS_CREATE_THREAD_REQUEST thread;
memset(&thread, 0, sizeof(thread));
thread.Entry = (uintptr_t)DoomAudioWorker;
thread.Priority = DOOM_AUDIO_WORKER_PRIORITY;
thread.ThreadHandleOut = &g_doomAudio.WorkerHandle;
thread.ThreadIdOut = &g_doomAudio.WorkerId;
status = SysCreateThread(&thread);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: audio worker creation failed; continuing without sound\n");
DoomAudioReleaseResources();
return;
}
memset(&thread, 0, sizeof(thread));
thread.Entry = (uintptr_t)DoomAudioSynthLoader;
thread.Priority = DOOM_AUDIO_SYNTH_LOADER_PRIORITY;
thread.ThreadHandleOut = &g_doomAudio.SynthLoaderHandle;
thread.ThreadIdOut = &g_doomAudio.SynthLoaderId;
status = SysCreateThread(&thread);
if (status != NTLSTATUS_SUCCESS)
DoomAudioPrint("doom: MIDI loader creation failed; continuing with sound effects only\n");
}
// Stops playback immediately without waiting for the producer thread.
void DoomAudioAbort(void) {
_InterlockedExchange(&g_doomAudio.StopRequested, 1);
if (g_doomAudio.StreamHandle)
(void)SysAudioStop(g_doomAudio.StreamHandle);
}
// Stops the producer before releasing its stream, synthesizer, and core lock.
void DoomAudioShutdown(void) {
DoomAudioAbort();
if (g_doomAudio.WorkerHandle) {
(void)SysWaitSingle(g_doomAudio.WorkerHandle,
SYS_WAIT_FLAG_NONE,
SYS_WAIT_TIMEOUT_INFINITE);
(void)SysCloseHandle(g_doomAudio.WorkerHandle);
g_doomAudio.WorkerHandle = 0;
}
if (g_doomAudio.SynthLoaderHandle) {
(void)SysWaitSingle(g_doomAudio.SynthLoaderHandle,
SYS_WAIT_FLAG_NONE,
SYS_WAIT_TIMEOUT_INFINITE);
(void)SysCloseHandle(g_doomAudio.SynthLoaderHandle);
g_doomAudio.SynthLoaderHandle = 0;
}
DoomAudioReleaseResources();
}
// Enters the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioLockCore(void) {
if (g_doomAudio.CoreMutex)
(void)SysWaitSingle(g_doomAudio.CoreMutex,
SYS_WAIT_FLAG_NONE,
SYS_WAIT_TIMEOUT_INFINITE);
}
// Leaves the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioUnlockCore(void) {
if (g_doomAudio.CoreMutex)
(void)SysReleaseMutex(g_doomAudio.CoreMutex);
}
+18
View File
@@ -0,0 +1,18 @@
// Doom audio owns PureDOOM sound extraction, MIDI synthesis, and native stream submission.
#pragma once
// Starts the optional audio worker after PureDOOM has initialized its sound state.
void DoomAudioStart(void);
// Stops playback immediately without waiting for the worker.
void DoomAudioAbort(void);
// Stops the audio worker and releases its process-owned resources.
void DoomAudioShutdown(void);
// Enters the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioLockCore(void);
// Leaves the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioUnlockCore(void);
+19
View File
@@ -0,0 +1,19 @@
Copyright (C) 2017-2023 Bernhard Schelling (Based on SFZero, Copyright (C) 2012 Steve Folta, https://github.com/stevefolta/SFZero)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+11
View File
@@ -0,0 +1,11 @@
TinySoundFont v0.9
Upstream: https://github.com/schellingb/TinySoundFont
File: tsf.h
License: MIT; see LICENSE.tsf.
The vendored header is unchanged apart from normalized line endings. nt-lite
supplies allocator, memory, and math macros from doom_audio.c so the synthesizer
stays within the native user runtime. Doom loads the optional small `doom.sf2`
asset asynchronously, then releases the input mapping after TinySoundFont has
copied the sample bank into its owned representation.
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -426,7 +426,7 @@ static void load_file(void) {
char buf[512];
size_t got = 0;
bool truncated = false;
for (;;) {
while (true) {
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(h, buf, sizeof(buf), &got);
if (SysReadFile(&readReq) != 0 || got == 0) break;
for (size_t i = 0; i < got; i++) {
+1 -1
View File
@@ -279,7 +279,7 @@ int main(void) {
char buf[513];
size_t total = 0;
for (;;) {
while (true) {
size_t got = 0;
st = SysRecvSocket(sock, buf, sizeof(buf) - 1, 0, &got);
if (st != NTLSTATUS_SUCCESS) {
+1 -1
View File
@@ -332,7 +332,7 @@ int main(void) {
bool matched = false;
uint8_t lastType = 0, lastCode = 0;
for (;;) {
while (true) {
uint8_t recvBuf[512];
SYS_SOCKET_ADDRESS from;
size_t fromSize = sizeof(from);
+550 -149
View File
@@ -12,6 +12,12 @@
#define MAX_PROCS 64 // Maximum number of processes we will fetch at once
#define MAX_THREADS 128
#define SHELL_PROMPT "> "
#define SHELL_GLYPH_WIDTH 8
#define SHELL_GLYPH_HEIGHT 16
#define SHELL_TEXT_FOREGROUND 0x00CCCCCCu
#define SHELL_TEXT_BACKGROUND 0x00012456u
#define SHELL_CURSOR_FOREGROUND 0x00000000u
#define SHELL_CURSOR_BACKGROUND 0x00E0E0E0u
// Output helpers.
static void print(const char* s) { SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) }; SysWriteConsole(&req); }
@@ -192,6 +198,20 @@ static void printStringWidth(const char* s, size_t width) {
static char gHistory[SHELL_HISTORY_DEPTH][MAX_CMD];
static size_t gHistoryCount;
typedef struct SHELL_EDITOR {
char* Buffer;
int Capacity;
int Length;
int Cursor;
} SHELL_EDITOR;
typedef struct SHELL_VISIBLE_CURSOR {
int Column;
int Row;
char Character;
bool Visible;
} SHELL_VISIBLE_CURSOR;
static const char* const gShellBuiltinCommands[] = {
"cd",
"clear",
@@ -201,6 +221,7 @@ static const char* const gShellBuiltinCommands[] = {
"dir",
"display",
"exit",
"fbbench",
"gpu",
"help",
"kill",
@@ -211,6 +232,7 @@ static const char* const gShellBuiltinCommands[] = {
"threads",
"time",
"type",
"volume",
"write",
};
@@ -280,23 +302,112 @@ static void shellHistoryAdd(const char* cmd) {
gHistoryCount++;
}
// Replaces the visible line while keeping the cursor at the end.
static void shellEditorReplaceLine(char* buf, int len, int* pos, const char* text) {
int oldPos = *pos;
for (int i = 0; i < oldPos; i++) print("\b");
for (int i = 0; i < oldPos; i++) print(" ");
for (int i = 0; i < oldPos; i++) print("\b");
// Restores the character underneath the visible edit cursor.
static void shellEditorHideVisibleCursor(SHELL_VISIBLE_CURSOR* cursor) {
if (!cursor || !cursor->Visible) return;
int newPos = 0;
if (text) {
while (text[newPos] && newPos < len - 1) {
buf[newPos] = text[newPos];
newPos++;
(void)SysPutScreenChar(cursor->Character,
cursor->Column * SHELL_GLYPH_WIDTH,
cursor->Row * SHELL_GLYPH_HEIGHT,
SHELL_TEXT_FOREGROUND,
SHELL_TEXT_BACKGROUND);
cursor->Visible = false;
}
// Draws the same steady block cursor used by edit without changing console state.
static void shellEditorShowVisibleCursor(SHELL_VISIBLE_CURSOR* cursor, const SHELL_EDITOR* editor) {
if (!cursor || !editor) return;
shellEditorHideVisibleCursor(cursor);
SYS_CONSOLE_INFO info;
if (SysGetConsoleInfo(&info) != NTLSTATUS_SUCCESS) return;
char character = editor->Cursor < editor->Length
? editor->Buffer[editor->Cursor]
: ' ';
if (SysPutScreenChar(character,
(int)info.CursorX * SHELL_GLYPH_WIDTH,
(int)info.CursorY * SHELL_GLYPH_HEIGHT,
SHELL_CURSOR_FOREGROUND,
SHELL_CURSOR_BACKGROUND) != NTLSTATUS_SUCCESS)
return;
cursor->Column = (int)info.CursorX;
cursor->Row = (int)info.CursorY;
cursor->Character = character;
cursor->Visible = true;
}
// Moves the visible cursor left by the requested number of cells.
static void shellEditorMoveLeftCells(int count) {
while (count-- > 0) print("\b");
}
// Moves the editor cursor without changing the buffer.
static void shellEditorMoveCursor(SHELL_EDITOR* editor, int target) {
if (!editor) return;
if (target < 0) target = 0;
if (target > editor->Length) target = editor->Length;
if (target < editor->Cursor) {
shellEditorMoveLeftCells(editor->Cursor - target);
} else {
while (editor->Cursor < target) {
char echo[2] = { editor->Buffer[editor->Cursor], '\0' };
print(echo);
editor->Cursor++;
}
}
buf[newPos] = '\0';
*pos = newPos;
print(buf);
editor->Cursor = target;
}
// Redraws the changed tail and restores the requested cursor position.
static void shellEditorRedrawTail(SHELL_EDITOR* editor, int start, int oldLength, int targetCursor) {
if (!editor) return;
print(editor->Buffer + start);
if (oldLength > editor->Length) printSpaces((size_t)(oldLength - editor->Length));
int displayedEnd = oldLength > editor->Length ? oldLength : editor->Length;
shellEditorMoveLeftCells(displayedEnd - targetCursor);
editor->Cursor = targetCursor;
}
// Replaces one buffer range and keeps the visible line synchronized.
static bool shellEditorReplaceRange(SHELL_EDITOR* editor, int start, int end, const char* text) {
if (!editor || !editor->Buffer || editor->Capacity <= 0 || !text) return false;
if (start < 0 || end < start || end > editor->Length) return false;
shellEditorMoveCursor(editor, start);
int oldLength = editor->Length;
int removed = end - start;
int available = editor->Capacity - 1 - (oldLength - removed);
int replacementLength = 0;
while (text[replacementLength] && replacementLength < available) replacementLength++;
memmove(editor->Buffer + start + replacementLength,
editor->Buffer + end,
(size_t)(oldLength - end + 1));
if (replacementLength > 0)
memcpy(editor->Buffer + start, text, (size_t)replacementLength);
editor->Length = oldLength - removed + replacementLength;
int targetCursor = start + replacementLength;
shellEditorRedrawTail(editor, start, oldLength, targetCursor);
return removed != replacementLength || replacementLength != 0;
}
// Inserts text at the current cursor position.
static bool shellEditorInsert(SHELL_EDITOR* editor, const char* text) {
if (!editor) return false;
return shellEditorReplaceRange(editor, editor->Cursor, editor->Cursor, text);
}
// Replaces the complete visible line and leaves the cursor at its end.
static void shellEditorReplaceLine(SHELL_EDITOR* editor, const char* text) {
if (!editor) return;
(void)shellEditorReplaceRange(editor, 0, editor->Length, text ? text : "");
}
// Returns true when a character separates shell tokens.
@@ -340,23 +451,6 @@ static void shellUpdateCommonPrefix(char* common, size_t commonSize, const char*
common[i] = '\0';
}
// Appends text to the edited line and echoes it.
static bool shellEditorAppend(char* buf, int len, int* pos, const char* text) {
if (!buf || len <= 0 || !pos || !text) return false;
bool changed = false;
while (*text && *pos < len - 1) {
buf[*pos] = *text;
(*pos)++;
buf[*pos] = '\0';
char echo[2] = { *text, '\0' };
print(echo);
text++;
changed = true;
}
return changed;
}
// Prints one completion candidate.
static void shellPrintCompletionCandidate(const char* name, bool directory) {
print(" ");
@@ -454,18 +548,23 @@ static void shellCollectPathCompletions(const char* token, size_t tokenLength,
}
// Redraws the prompt and current edit buffer after a completion list.
static void shellRedrawPromptAndLine(const char* buf) {
static void shellRedrawPromptAndLine(SHELL_EDITOR* editor) {
print(SHELL_PROMPT);
print(buf ? buf : "");
if (!editor) return;
print(editor->Buffer);
shellEditorMoveLeftCells(editor->Length - editor->Cursor);
}
// Completes the token at the end of the current line.
static void shellCompleteLine(char* buf, int len, int* pos) {
if (!buf || len <= 0 || !pos || *pos < 0) return;
// Completes the token around the current cursor position.
static void shellCompleteLine(SHELL_EDITOR* editor) {
if (!editor || !editor->Buffer || editor->Capacity <= 0 || editor->Cursor < 0) return;
size_t cursor = (size_t)*pos;
char* buf = editor->Buffer;
size_t cursor = (size_t)editor->Cursor;
size_t tokenStart = cursor;
while (tokenStart > 0 && !shellIsTokenSeparator(buf[tokenStart - 1])) tokenStart--;
size_t tokenEnd = cursor;
while (tokenEnd < (size_t)editor->Length && !shellIsTokenSeparator(buf[tokenEnd])) tokenEnd++;
const char* token = buf + tokenStart;
size_t tokenLength = cursor - tokenStart;
@@ -490,14 +589,20 @@ static void shellCompleteLine(char* buf, int len, int* pos) {
size_t commonLength = strlen(state.Common);
bool expanded = commonLength > namePrefixLength;
if (expanded) {
shellEditorAppend(buf, len, pos, state.Common + namePrefixLength);
(void)shellEditorReplaceRange(editor, editor->Cursor, (int)tokenEnd,
state.Common + namePrefixLength);
}
if (state.MatchCount == 1) {
if (state.LastIsDirectory) {
if (*pos < len - 1 && *pos > 0 && !shellIsPathSeparator(buf[*pos - 1])) shellEditorAppend(buf, len, pos, "/");
if (editor->Cursor < editor->Capacity - 1 && editor->Cursor > 0 &&
!shellIsPathSeparator(buf[editor->Cursor - 1]))
(void)shellEditorInsert(editor, "/");
} else if (state.LastAddsSpace) {
if (*pos < len - 1 && (*pos == 0 || buf[*pos - 1] != ' ')) shellEditorAppend(buf, len, pos, " ");
if (editor->Cursor < editor->Capacity - 1 &&
(editor->Cursor == editor->Length ||
!shellIsTokenSeparator(buf[editor->Cursor])))
(void)shellEditorInsert(editor, " ");
}
return;
}
@@ -509,17 +614,27 @@ static void shellCompleteLine(char* buf, int len, int* pos) {
shellCollectBuiltinCompletions(token, tokenLength, NULL, true);
}
shellCollectPathCompletions(token, tokenLength, NULL, true);
shellRedrawPromptAndLine(buf);
shellRedrawPromptAndLine(editor);
}
// Reads one editable command line from the keyboard.
static bool readLine(char* buf, int len) {
int pos = 0;
SHELL_EDITOR editor;
SHELL_VISIBLE_CURSOR visibleCursor;
char historyDraft[MAX_CMD];
size_t historyCursor = gHistoryCount;
if (len <= 0) return false;
editor.Buffer = buf;
editor.Capacity = len;
editor.Length = 0;
editor.Cursor = 0;
memset(&visibleCursor, 0, sizeof(visibleCursor));
buf[0] = '\0';
historyDraft[0] = '\0';
while (true) {
shellEditorShowVisibleCursor(&visibleCursor, &editor);
INPUT_KEY_RECORD input;
SYS_INPUT_EVENT_REQUEST inputReq;
inputReq.Size = sizeof(inputReq);
@@ -527,7 +642,9 @@ static bool readLine(char* buf, int len) {
inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE;
inputReq.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
inputReq.RecordOut = &input;
if (SysReadInputEvent(&inputReq) != NTLSTATUS_SUCCESS) { SysYield(); continue; }
KSTATUS inputStatus = SysReadInputEvent(&inputReq);
shellEditorHideVisibleCursor(&visibleCursor);
if (inputStatus != NTLSTATUS_SUCCESS) { SysYield(); continue; }
INPUT_KEY key = input.Key;
if (key == INPUT_KEY_NONE) { SysYield(); continue; }
@@ -545,18 +662,45 @@ static bool readLine(char* buf, int len) {
}
if (key == INPUT_KEY_BACKSPACE) {
if (pos > 0) {
pos--;
buf[pos] = '\0';
print("\b \b");
}
if (editor.Cursor > 0)
(void)shellEditorReplaceRange(&editor, editor.Cursor - 1, editor.Cursor, "");
continue;
}
if (key == INPUT_KEY_DELETE) {
if (editor.Cursor < editor.Length)
(void)shellEditorReplaceRange(&editor, editor.Cursor, editor.Cursor + 1, "");
continue;
}
if (key == INPUT_KEY_LEFT) {
shellEditorMoveCursor(&editor, editor.Cursor - 1);
continue;
}
if (key == INPUT_KEY_RIGHT) {
shellEditorMoveCursor(&editor, editor.Cursor + 1);
continue;
}
if (key == INPUT_KEY_HOME) {
shellEditorMoveCursor(&editor, 0);
continue;
}
if (key == INPUT_KEY_END) {
shellEditorMoveCursor(&editor, editor.Length);
continue;
}
if (key == INPUT_KEY_UP) {
if (historyCursor > 0) {
if (historyCursor == gHistoryCount) {
strncpy(historyDraft, editor.Buffer, sizeof(historyDraft) - 1);
historyDraft[sizeof(historyDraft) - 1] = '\0';
}
historyCursor--;
shellEditorReplaceLine(buf, len, &pos, gHistory[historyCursor]);
shellEditorReplaceLine(&editor, gHistory[historyCursor]);
}
continue;
}
@@ -564,25 +708,21 @@ static bool readLine(char* buf, int len) {
if (key == INPUT_KEY_DOWN) {
if (historyCursor < gHistoryCount) {
historyCursor++;
shellEditorReplaceLine(buf, len, &pos,
historyCursor < gHistoryCount ? gHistory[historyCursor] : "");
shellEditorReplaceLine(&editor,
historyCursor < gHistoryCount ? gHistory[historyCursor] : historyDraft);
}
continue;
}
if (key == INPUT_KEY_TAB) {
shellCompleteLine(buf, len, &pos);
shellCompleteLine(&editor);
continue;
}
char c = input.Char ? input.Char : shellKeyToChar(key, modifiers);
if (c < 0x20 || c == 127) continue;
if (pos < len - 1) {
buf[pos++] = c;
buf[pos] = '\0';
char echo[2] = { c, '\0' };
print(echo);
}
char text[2] = { c, '\0' };
(void)shellEditorInsert(&editor, text);
}
return false;
@@ -604,6 +744,7 @@ static void cmd_help() {
" mem - Show memory usage\n"
" time - Show system wall-clock time\n"
" display - Show display provider and counters\n"
" fbbench - Benchmark Doom-size and full-screen presents\n"
" stats - Show scheduler/CPU/MM telemetry\n"
" ps - List processes\n"
" threads <pid> - Show one process's thread statuses\n"
@@ -611,6 +752,9 @@ static void cmd_help() {
" dir [path] - List directory contents\n"
" cd <path> - Change directory\n"
" type <file> - Display file contents\n"
" volume [0-100] - Query or set master audio volume\n"
" volume mute - Mute the default audio endpoint\n"
" volume unmute - Unmute the default audio endpoint\n"
" del <file> - Delete a file\n"
" write <file> <tx> - Create a file with text\n"
" clear - Clear the screen\n"
@@ -639,6 +783,117 @@ static void printCpuFlags(uint32_t flags) {
if (flags & SYS_CPU_FLAG_PRESENT) print("present ");
}
// Selects the available default audio endpoint, or the first available endpoint.
static bool shellQueryDefaultAudioEndpoint(SYS_AUDIO_ENDPOINT_INFO* endpointOut) {
if (!endpointOut) return false;
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
uint32_t count = 0;
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Endpoints = endpoints;
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
request.CountOut = &count;
KSTATUS status = SysAudioEnumerateEndpoints(&request);
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_BUFFER_TOO_SMALL) {
print("volume: endpoint query failed status=");
printUInt(status);
print("\n");
return false;
}
const SYS_AUDIO_ENDPOINT_INFO* fallback = NULL;
uint32_t availableCount = count < SYS_AUDIO_MAX_ENDPOINTS
? count
: SYS_AUDIO_MAX_ENDPOINTS;
for (uint32_t index = 0; index < availableCount; index++) {
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
if (endpoint->Size != sizeof(*endpoint) ||
endpoint->Version != SYS_AUDIO_ABI_VERSION ||
endpoint->State != SysAudioEndpointReady ||
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
continue;
if (!fallback) fallback = endpoint;
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT) {
*endpointOut = *endpoint;
return true;
}
}
if (fallback) {
*endpointOut = *fallback;
return true;
}
print("volume: no available audio endpoint\n");
return false;
}
// Converts one public Q16 gain into a rounded percentage.
static uint32_t shellAudioVolumePercent(uint32_t volumeQ16) {
uint64_t scaled = (uint64_t)volumeQ16 * 100u + SYS_AUDIO_GAIN_Q16_UNITY / 2u;
return (uint32_t)(scaled / SYS_AUDIO_GAIN_Q16_UNITY);
}
// Converts one percentage into public Q16 gain.
static uint32_t shellAudioVolumeQ16(uint32_t percent) {
return (uint32_t)(((uint64_t)percent * SYS_AUDIO_GAIN_Q16_UNITY + 50u) / 100u);
}
// Queries or updates the default endpoint master volume.
static void cmd_volume(const char* args) {
SYS_AUDIO_ENDPOINT_INFO endpoint;
if (!shellQueryDefaultAudioEndpoint(&endpoint)) return;
while (args && (*args == ' ' || *args == '\t')) args++;
if (!args || *args == '\0') {
print("Volume: ");
printUInt(shellAudioVolumePercent(endpoint.MasterVolumeQ16));
print("%");
if (endpoint.MasterMuted) print(" (muted)");
print("\n");
return;
}
uint32_t volumeQ16 = endpoint.MasterVolumeQ16;
uint32_t muted = endpoint.MasterMuted ? 1u : 0u;
if (strcmp(args, "mute") == 0) {
muted = 1u;
} else if (strcmp(args, "unmute") == 0) {
muted = 0u;
} else {
uint32_t percent = 0;
if (!parseUInt(args, &percent) || percent > 100u) {
print("Usage: volume [0-100|mute|unmute]\n");
return;
}
volumeQ16 = shellAudioVolumeQ16(percent);
muted = 0u;
}
SYS_AUDIO_SET_MASTER_VOLUME_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.EndpointId = endpoint.EndpointId;
request.Muted = muted;
request.VolumeQ16 = volumeQ16;
KSTATUS status = SysAudioSetMasterVolume(&request);
if (status != NTLSTATUS_SUCCESS) {
print("volume: update failed status=");
printUInt(status);
print("\n");
return;
}
print("Volume: ");
printUInt(shellAudioVolumePercent(volumeQ16));
print("%");
if (muted) print(" (muted)");
print("\n");
}
// Implements the cmd stats operation.
static void cmd_stats(void) {
SYS_SCHEDULER_STATS sched;
@@ -801,6 +1056,12 @@ static void cmd_stats(void) {
print(" voluntary blocks: "); printUInt64(sched.VoluntaryBlocks); print("\n");
print(" SMT deferrals: "); printUInt64(sched.SmtSiblingDeferrals); print("\n");
print(" SMT primary pokes: "); printUInt64(sched.SmtPrimaryPokes); print("\n");
print(" RT dispatches: "); printUInt64(sched.RealtimeDispatches); print("\n");
print(" RT preemptions: "); printUInt64(sched.RealtimePreemptions); print("\n");
print(" PI boosts: "); printUInt64(sched.PriorityInheritanceBoosts); print("\n");
print(" PI drops: "); printUInt64(sched.PriorityInheritanceDrops); print("\n");
print(" PI propagations: "); printUInt64(sched.PriorityInheritancePropagations); print("\n");
print(" PI max depth: "); printUInt64(sched.PriorityInheritanceMaxDepth); print("\n");
print("Exceptions:\n");
print(" delivered: "); printUInt64(exceptionStats.Delivered); print("\n");
@@ -975,20 +1236,6 @@ static void cmd_stats(void) {
print(" standby="); printUInt64(fileCache.StandbyTransitions);
print(" standby-hits="); printUInt64(fileCache.StandbyHits);
print(" drop-fail="); printUInt64(fileCache.DropFailures); print("\n");
print(" read-ahead: requests="); printUInt64(fileCache.ReadAheadRequests);
print(" requested="); printUInt64(fileCache.ReadAheadPagesRequested);
print(" queued="); printUInt64(fileCache.ReadAheadPagesQueued);
print(" loaded="); printUInt64(fileCache.ReadAheadPagesLoaded);
print(" skipped="); printUInt64(fileCache.ReadAheadPagesSkipped);
print(" fail="); printUInt64(fileCache.ReadAheadFailures);
print(" throttled="); printUInt64(fileCache.ReadAheadPagesThrottled); print("\n");
print(" read-ahead max: pages/request="); printUInt64(fileCache.MaxReadAheadPagesPerRequest); print("\n");
print(" batch fills: reads="); printUInt64(fileCache.BatchReads);
print(" requested="); printUInt64(fileCache.BatchPagesRequested);
print(" loaded="); printUInt64(fileCache.BatchPagesLoaded);
print(" max-request="); printUInt64(fileCache.MaxBatchPagesRequested);
print(" max-loaded="); printUInt64(fileCache.MaxBatchPagesLoaded); print("\n");
print("Filesystem:\n");
print(" path cache: lookups="); printUInt64(filesystem.PathLookups);
print(" hits="); printUInt64(filesystem.PathCacheHits);
@@ -1012,18 +1259,6 @@ static void cmd_stats(void) {
print(" writes: requests="); printUInt64(disk.WriteRequests);
print(" sectors="); printUInt64(disk.WriteSectors);
print(" max-sectors="); printUInt64(disk.MaxWriteSectors); print("\n");
print(" read-ahead: hits="); printUInt64(disk.ReadaheadHits);
print(" misses="); printUInt64(disk.ReadaheadMisses);
print(" stores="); printUInt64(disk.ReadaheadStores);
print(" sectors="); printUInt64(disk.ReadaheadSectorsLoaded);
print(" max-sectors="); printUInt64(disk.MaxReadaheadSectors); print("\n");
print(" read-ahead misc: served-bytes="); printUInt64(disk.ReadaheadBytesServed);
print(" seq="); printUInt64(disk.ReadaheadSequentialDetections);
print(" invalidations="); printUInt64(disk.ReadaheadInvalidations);
print(" suppressed="); printUInt64(disk.ReadaheadSuppressed);
print(" alloc-fail="); printUInt64(disk.ReadaheadAllocFailures);
print(" read-fail="); printUInt64(disk.ReadaheadReadFailures); print("\n");
print("Surfaces:\n");
print(" lifetime: created="); printUInt64(surface.CreatedSurfaces);
print(" destroyed="); printUInt64(surface.DestroyedSurfaces);
@@ -1123,9 +1358,11 @@ static void printDisplayStats(const char* title, const DISPLAY_STATS* st) {
print(" fill: "); printUInt64(st->FillRectCount); print("\n");
print(" copy: "); printUInt64(st->CopyRectCount); print("\n");
print(" read: "); printUInt64(st->ReadRectCount); print("\n");
print(" present: "); printUInt64(st->PresentCount); print(" rects="); printUInt64(st->PresentRectCount); print("\n");
print(" present: "); printUInt64(st->PresentCount); print(" rects="); printUInt64(st->PresentRectCount);
print(" direct="); printUInt64(st->DirectPresentCount); print("\n");
print(" dirty: "); printUInt64(st->DirtyRectCount); print(" all="); printUInt64(st->DirtyAllCount); print("\n");
print(" bytes: "); printUInt64(st->BytesCopied); print("\n");
print(" bytes: logical="); printUInt64(st->BytesCopied);
print(" scanout="); printUInt64(st->ScanoutBytes); print("\n");
print(" sync: "); printUInt64(st->SyncCount); print("\n");
print(" cursor: move="); printUInt64(st->CursorMoveCount);
print(" show="); printUInt64(st->CursorShowCount);
@@ -1149,10 +1386,205 @@ static void cmd_display(void) {
print(" mode: "); printUInt(info.Mode.Width); print("x"); printUInt(info.Mode.Height);
print(" stride="); printUInt(info.Mode.Stride); print(" format="); printUInt((uint32_t)info.Mode.Format); print("\n");
print(" caps: 0x"); printHex32(info.Caps); print(" ["); printDisplayCaps(info.Caps); print("]\n");
print(" scanout: ");
print((info.Flags & DISPLAY_INFO_FLAG_PAT_WC_AVAILABLE) ? "PAT WC available" : "PAT WC unavailable");
print("\n");
printDisplayStats("Manager counters", &info.ManagerStats);
printDisplayStats("Provider counters", &info.DeviceStats);
}
typedef struct SHELL_FB_BENCH_RESULT {
uint32_t Width;
uint32_t Height;
uint32_t Frames;
uint32_t Status;
uint64_t ElapsedTicks;
uint64_t Frequency;
uint64_t ExpectedBytes;
uint64_t ScanoutBytes;
uint64_t DirectPresents;
} SHELL_FB_BENCH_RESULT;
// Computes a decimal fixed-point ratio with one digit after the decimal point.
static uint64_t shellRatioTenths(uint64_t numerator, uint64_t denominator) {
if (!denominator)
return 0;
if (numerator > UINT64_MAX / 10u)
return (numerator / denominator) * 10u;
return (numerator * 10u) / denominator;
}
// Fills a benchmark surface with a deterministic high-contrast pattern.
static void shellFillBenchmarkSurface(uint32_t* pixels,
uint32_t pitchPixels,
uint32_t width,
uint32_t height) {
for (uint32_t y = 0; y < height; y++) {
uint32_t* row = pixels + (size_t)y * pitchPixels;
for (uint32_t x = 0; x < width; x++) {
uint32_t red = (x * 255u) / (width ? width : 1u);
uint32_t green = (y * 255u) / (height ? height : 1u);
uint32_t blue = ((x ^ y) & 0xFFu);
row[x] = (red << 16) | (green << 8) | blue;
}
}
}
// Runs one synchronous surface-present benchmark case.
static void shellRunFramebufferBenchmark(uint32_t width,
uint32_t height,
uint32_t frames,
uint64_t frequency,
SHELL_FB_BENCH_RESULT* result) {
memset(result, 0, sizeof(*result));
result->Width = width;
result->Height = height;
result->Frames = frames;
result->Frequency = frequency;
uint32_t surface = 0;
SYS_CREATE_SURFACE_REQUEST create;
memset(&create, 0, sizeof(create));
create.Size = sizeof(create);
create.Width = width;
create.Height = height;
create.Format = SYS_SURFACE_FORMAT_XRGB8888;
create.Flags = SYS_SURFACE_FLAG_NONE;
create.HandleOut = &surface;
uint32_t status = SysCreateSurface(&create);
if (status != NTLSTATUS_SUCCESS || !surface) {
result->Status = status ? status : NTLSTATUS_FAILURE;
return;
}
void* base = 0;
uint32_t pitchBytes = 0;
status = SysMapSurface(surface, &base, &pitchBytes);
if (status != NTLSTATUS_SUCCESS || !base || pitchBytes < width * sizeof(uint32_t)) {
result->Status = status ? status : NTLSTATUS_FAILURE;
SysCloseHandle(surface);
return;
}
shellFillBenchmarkSurface((uint32_t*)base, pitchBytes / sizeof(uint32_t), width, height);
DISPLAY_RECT rect = { 0, 0, (int32_t)width, (int32_t)height };
for (uint32_t i = 0; i < 2; i++) {
status = SysPresentSurface(surface, &rect, 1);
if (status != NTLSTATUS_SUCCESS)
break;
}
SYS_DISPLAY_INFO before;
SYS_DISPLAY_INFO after;
memset(&before, 0, sizeof(before));
memset(&after, 0, sizeof(after));
if (status == NTLSTATUS_SUCCESS)
status = SysQueryDisplayInfo(&before);
uint64_t begin = 0;
uint64_t end = 0;
uint32_t completed = 0;
if (status == NTLSTATUS_SUCCESS) {
begin = SysGetTick();
for (; completed < frames; completed++) {
status = SysPresentSurface(surface, &rect, 1);
if (status != NTLSTATUS_SUCCESS)
break;
}
end = SysGetTick();
uint32_t queryStatus = SysQueryDisplayInfo(&after);
if (status == NTLSTATUS_SUCCESS && queryStatus != NTLSTATUS_SUCCESS)
status = queryStatus;
}
result->Frames = completed;
result->Status = status;
result->ElapsedTicks = end - begin;
result->ExpectedBytes = (uint64_t)width * (uint64_t)height * sizeof(uint32_t) * completed;
if (after.DeviceStats.ScanoutBytes >= before.DeviceStats.ScanoutBytes)
result->ScanoutBytes = after.DeviceStats.ScanoutBytes - before.DeviceStats.ScanoutBytes;
if (after.DeviceStats.DirectPresentCount >= before.DeviceStats.DirectPresentCount)
result->DirectPresents = after.DeviceStats.DirectPresentCount - before.DeviceStats.DirectPresentCount;
SysCloseHandle(surface);
}
// Prints one framebuffer benchmark result.
static void shellPrintFramebufferBenchmark(const char* name,
const SHELL_FB_BENCH_RESULT* result) {
print(" "); print(name); print(" ");
printUInt(result->Width); print("x"); printUInt(result->Height); print(": ");
if (result->Status != NTLSTATUS_SUCCESS || !result->ElapsedTicks || !result->Frequency) {
print("failed status=0x"); printHex32(result->Status); print("\n");
return;
}
uint64_t elapsedUs = SysTicksToMicroseconds(result->ElapsedTicks, result->Frequency);
uint64_t fpsTenths = shellRatioTenths((uint64_t)result->Frames * result->Frequency,
result->ElapsedTicks);
uint64_t bytes = result->ScanoutBytes ? result->ScanoutBytes : result->ExpectedBytes;
uint64_t mibTenths = shellRatioTenths(bytes * result->Frequency,
result->ElapsedTicks * 1024u * 1024u);
print("frames="); printUInt(result->Frames);
print(" time="); printUInt64(elapsedUs / 1000u); print(" ms");
print(" fps="); printUInt64(fpsTenths / 10u); print("."); printUInt64(fpsTenths % 10u);
print(" scanout="); printUInt64(mibTenths / 10u); print("."); printUInt64(mibTenths % 10u); print(" MiB/s");
print(" direct="); printUInt64(result->DirectPresents);
print(" bytes="); printUInt64(bytes);
print("\n");
}
// Benchmarks the synchronous surface-to-scanout path without console traffic.
static void cmd_fbbench(void) {
SYS_DISPLAY_INFO info;
uint32_t status = SysQueryDisplayInfo(&info);
if (status != NTLSTATUS_SUCCESS) {
print("fbbench: display query failed status=0x"); printHex32(status); print("\n");
return;
}
uint64_t frequency = 0;
status = SysGetTickFrequency(&frequency);
if (status != NTLSTATUS_SUCCESS || !frequency) {
print("fbbench: timer query failed status=0x"); printHex32(status); print("\n");
return;
}
uint32_t doomWidth = info.Mode.Width < 960u ? info.Mode.Width : 960u;
uint32_t doomHeight = info.Mode.Height < 600u ? info.Mode.Height : 600u;
uint32_t fullWidth = info.Mode.Width;
uint32_t fullHeight = info.Mode.Height;
uint64_t targetBytes = 128ull * 1024ull * 1024ull;
uint64_t doomFrameBytes = (uint64_t)doomWidth * doomHeight * sizeof(uint32_t);
uint64_t fullFrameBytes = (uint64_t)fullWidth * fullHeight * sizeof(uint32_t);
uint32_t doomFrames = doomFrameBytes ? (uint32_t)((targetBytes + doomFrameBytes - 1u) / doomFrameBytes) : 1u;
uint32_t fullFrames = fullFrameBytes ? (uint32_t)((targetBytes + fullFrameBytes - 1u) / fullFrameBytes) : 1u;
if (doomFrames < 8u) doomFrames = 8u;
if (doomFrames > 128u) doomFrames = 128u;
if (fullFrames < 8u) fullFrames = 8u;
if (fullFrames > 64u) fullFrames = 64u;
print("fbbench: running; the framebuffer will be overwritten temporarily\n");
SHELL_FB_BENCH_RESULT doom;
SHELL_FB_BENCH_RESULT full;
shellRunFramebufferBenchmark(doomWidth, doomHeight, doomFrames, frequency, &doom);
shellRunFramebufferBenchmark(fullWidth, fullHeight, fullFrames, frequency, &full);
SysClearScreen(0x00012456u);
print("Framebuffer benchmark:\n");
print(" provider: "); print(info.PrimaryName[0] ? info.PrimaryName : "?");
print(" mode="); printUInt(info.Mode.Width); print("x"); printUInt(info.Mode.Height);
print(" stride="); printUInt(info.Mode.Stride);
print(" cache="); print((info.Flags & DISPLAY_INFO_FLAG_PAT_WC_AVAILABLE) ? "PAT-WC" : "fallback");
print("\n");
shellPrintFramebufferBenchmark("doom", &doom);
shellPrintFramebufferBenchmark("full", &full);
}
// Implements the cmd mem operation.
static void cmd_mem(void) {
SYS_MEMORY_STATUS st;
@@ -1200,6 +1632,7 @@ static const char* threadStateName(uint32_t state) {
case SYS_THREAD_STATE_RUNNING: return "run";
case SYS_THREAD_STATE_WAITING: return "wait";
case SYS_THREAD_STATE_DEAD: return "dead";
case SYS_THREAD_STATE_SUSPENDED: return "suspend";
default: return "?";
}
}
@@ -1331,7 +1764,7 @@ static void cmd_threads(const char* args) {
print(" (Count: ");
printUInt((uint32_t)count);
print("):\n");
print(" TID State Pri CPU LastCPU QRem QTick Burst Wait WStat Flags\n");
print(" TID State Base Eff CPU LastCPU QRem QTick Burst Wait WStat Flags\n");
print(" Flags: *=current I=idle K=kernel Q=queued A=alertable k=kernel-apc u=user-apc.\n");
size_t displayCount = count < MAX_THREADS ? count : MAX_THREADS;
@@ -1342,7 +1775,11 @@ static void cmd_threads(const char* args) {
print(" ");
printStringWidth(threadStateName(t->State), 5);
print(" ");
printUIntWidth(t->Priority, 3);
if (t->BaseSchedulingClass == SYS_SCHEDULING_REALTIME) print("R"); else print("N");
printUIntWidth(t->BasePriority, 2);
print(" ");
if (t->EffectiveSchedulingClass == SYS_SCHEDULING_REALTIME) print("R"); else print("N");
printUIntWidth(t->EffectivePriority, 2);
print(" ");
if (t->RunningCpu == 0xffffffffu) printStringWidth("-", 3);
else printUIntWidth(t->RunningCpu, 3);
@@ -1431,7 +1868,7 @@ static int shellCompareNames(const char* a, const char* b) {
if (!a) a = "";
if (!b) b = "";
for (;;) {
while (true) {
char ca = shellAsciiLower(a[i]);
char cb = shellAsciiLower(b[i]);
if (ca != cb)
@@ -1540,7 +1977,7 @@ static void cmd_dir(const char* path) {
uint32_t count = 0;
uint32_t capacity = 0;
for (;;) {
while (true) {
SYS_DIR_ENTRY ke;
st = SysReadDir(hDir, &ke);
if (st != 0)
@@ -1575,7 +2012,7 @@ static void cmd_type(const char* filename) {
uint32_t hFile;
if (open_file(filename, &hFile) != 0) { print("type: cannot open file\n"); return; }
char buffer[512]; size_t bytesRead;
for (;;) {
while (true) {
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(hFile, buffer, sizeof(buffer) - 1, &bytesRead);
if (SysReadFile(&readReq) != 0 || bytesRead == 0) break;
buffer[bytesRead] = '\0'; print(buffer);
@@ -1604,78 +2041,39 @@ static bool extractProgramPath(const char* cmdline, char* out, size_t outSize) {
return pos != 0;
}
// Consumes queued Ctrl-C input without taking ordinary foreground stdin.
static bool shellConsumeQueuedCtrlC(void) {
for (int i = 0; i < 4; i++) {
INPUT_KEY_RECORD input;
SYS_INPUT_EVENT_REQUEST inputReq;
inputReq.Size = sizeof(inputReq);
inputReq.QueueHandle = 0;
inputReq.Flags = SYS_INPUT_EVENT_FLAG_NONE;
inputReq.TimeoutMilliseconds = 0;
inputReq.RecordOut = &input;
if (SysPeekInputEvent(&inputReq) != NTLSTATUS_SUCCESS) return false;
if ((input.Modifiers & INPUT_MOD_CTRL) && input.Key == INPUT_KEY_C) {
(void)SysReadInputEvent(&inputReq);
return true;
}
if (input.Key == INPUT_KEY_LEFT_CTRL || input.Key == INPUT_KEY_RIGHT_CTRL) {
(void)SysReadInputEvent(&inputReq);
continue;
}
return false;
}
return false;
}
// Returns whether one input key is currently held.
static bool shellIsKeyDown(INPUT_KEY key) {
bool down = false;
return SysIsKeyDown(key, &down) == NTLSTATUS_SUCCESS && down;
}
// Waits for a foreground process while watching Ctrl-C without stealing ordinary stdin.
// Waits for a foreground process while polling Ctrl-C without consuming child input.
static uint32_t waitForegroundProcess(uint32_t hProc) {
bool sentBreak = false;
uint32_t inputQueue = 0;
if (SysOpenInputQueue(0, &inputQueue) != 0)
return SysWait(hProc, SYS_WAIT_FLAG_NONE);
uint32_t handles[2] = { hProc, inputQueue };
uint32_t index = 0;
SYS_WAIT_MANY_REQUEST waitReq;
waitReq.Size = sizeof(waitReq);
waitReq.Flags = SYS_WAIT_FLAG_NONE;
waitReq.Handles = handles;
waitReq.Count = 2;
waitReq.SignaledIndexOut = &index;
waitReq.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
for (;;) {
index = 0;
uint32_t status = SysWaitMany(&waitReq);
if (status != NTLSTATUS_SUCCESS) {
SysCloseHandle(inputQueue);
while (true) {
uint32_t status = SysWaitSingle(hProc,
SYS_WAIT_FLAG_NONE,
SHELL_FOREGROUND_POLL_MS);
if (status == NTLSTATUS_SUCCESS)
return status;
}
if (index == 0) {
SysCloseHandle(inputQueue);
if (status != NTLSTATUS_TIMEOUT)
return status;
}
uint32_t modifiers = SysGetKeyModifiers();
if (((modifiers & INPUT_MOD_CTRL) && shellIsKeyDown(INPUT_KEY_C)) || shellConsumeQueuedCtrlC()) {
if (!sentBreak) {
print("^C\n");
(void)SysKillProcess(hProc, (int32_t)NTLSTATUS_PROCESS_KILLED);
sentBreak = true;
if (!sentBreak && (modifiers & INPUT_MOD_CTRL) &&
shellIsKeyDown(INPUT_KEY_C)) {
print("^C\n");
uint32_t killStatus = SysKillProcess(
hProc,
(int32_t)NTLSTATUS_PROCESS_KILLED);
if (killStatus != NTLSTATUS_SUCCESS) {
print("shell: foreground termination failed status=");
printUInt(killStatus);
print("\n");
return killStatus;
}
sentBreak = true;
}
}
}
@@ -1727,6 +2125,9 @@ static int runCommand(const char* cmd) {
if (strcmp(cmd, "mem") == 0) { cmd_mem(); return 0; }
if (strcmp(cmd, "time") == 0 || strcmp(cmd, "date") == 0) { cmd_time(); return 0; }
if (strcmp(cmd, "display") == 0 || strcmp(cmd, "gpu") == 0) { cmd_display(); return 0; }
if (strcmp(cmd, "fbbench") == 0) { cmd_fbbench(); return 0; }
if (strcmp(cmd, "volume") == 0) { cmd_volume(""); return 0; }
if (strncmp(cmd, "volume ", 7) == 0) { cmd_volume(cmd + 7); return 0; }
if (strcmp(cmd, "stats") == 0 || strcmp(cmd, "telemetry") == 0) { cmd_stats(); return 0; }
if (strcmp(cmd, "ps") == 0) { cmd_ps(); return 0; }
if (strncmp(cmd, "threads ", 8) == 0) { cmd_threads(cmd + 8); return 0; }
+1 -1
View File
@@ -142,7 +142,7 @@ static int shellCompareNames(const char* a, const char* b) {
if (!a) a = "";
if (!b) b = "";
for (;;) {
while (true) {
char ca = shellAsciiLower(a[i]);
char cb = shellAsciiLower(b[i]);
if (ca != cb)
+1 -1
View File
@@ -112,7 +112,7 @@ static int shellCompareNames(const char* a, const char* b) {
if (!a) a = "";
if (!b) b = "";
for (;;) {
while (true) {
char ca = shellAsciiLower(a[i]);
char cb = shellAsciiLower(b[i]);
if (ca != cb)
+55 -3
View File
@@ -50,11 +50,11 @@ static void fail_status(const char* what, uint32_t status) {
print(" failed status=");
print_u64(status);
print("\n");
for (;;) SysExitProcess(1);
while (true) SysExitProcess(1);
}
static void exit_now(int code) {
for (;;) SysExitProcess(code);
while (true) SysExitProcess(code);
}
static SYS_STRING_VIEW command_line_view(void) {
@@ -227,6 +227,16 @@ int main(void) {
print("video: input file mapped\n");
if (!BinkSoundUseWaveOut()) {
print("video: BinkSoundUseWaveOut failed: ");
const char* err = BinkGetError();
print(err ? err : "unknown");
print("\n");
SysFreeVm(buf);
exit_now(1);
}
print("video: WaveOut sound system ready\n");
HBINK b = BinkOpen((const char*)buf, BINKFROMMEMORY);
if (!b) {
print("video: BinkOpen failed: ");
@@ -237,6 +247,25 @@ int main(void) {
exit_now(1);
}
print("video: Bink open tracks=");
print_u64((uint64_t)(b->NumTracks < 0 ? 0 : b->NumTracks));
print(" playing=");
print_u64(b->playingtracks);
print(" soundon=");
print_u64(b->soundon);
print("\n");
for (int32_t track = 0; track < b->NumTracks; track++) {
print("video: track ");
print_u64((uint64_t)track);
print(" id=");
print_u64(BinkGetTrackID(b, (uint32_t)track));
print(" type=");
print_u64(BinkGetTrackType(b, (uint32_t)track));
print(" maxBytes=");
print_u64(BinkGetTrackMaxSize(b, (uint32_t)track));
print("\n");
}
uint32_t width = b->Width;
uint32_t height = b->Height;
if (!width || !height) {
@@ -273,12 +302,16 @@ int main(void) {
}
print("video: native surface ready\n");
print("video: decode loop entered\n");
DISPLAY_RECT fullFrame = { 0, 0, (int32_t)width, (int32_t)height };
int firstFramePresented = 0;
while (1) {
if (BinkWait(b)) {
_mm_pause();
// Bink can be waiting on its decode, I/O, or WaveOut worker. Yield
// a millisecond instead of monopolizing the CPU with a hot poll.
SysSleep(1);
continue;
}
@@ -299,6 +332,16 @@ int main(void) {
SysFreeVm(buf);
fail_status("SysPresentSurface", status);
}
if (!firstFramePresented) {
firstFramePresented = 1;
print("video: first frame presented sndBytes=");
print_u64(b->SndSize);
print(" soundon=");
print_u64(b->soundon);
print(" playing=");
print_u64(b->playingtracks);
print("\n");
}
if (b->FrameNum == b->Frames)
break;
@@ -306,8 +349,17 @@ int main(void) {
BinkNextFrame(b);
}
print("video: playback complete frame=");
print_u64(b->FrameNum);
print(" sndBytes=");
print_u64(b->SndSize);
print("\n");
print("video: closing surface\n");
SysCloseHandle(surface);
print("video: closing Bink\n");
BinkClose(b);
print("video: Bink closed\n");
print("video: freeing input mapping\n");
SysFreeVm(buf);
print("video: goodbye\n");