Loaded and returned audio banks. Let's see if this actually works, though...
This commit is contained in:
@@ -7,9 +7,9 @@ else
|
||||
CC := cc
|
||||
LDFLAGS := -lm -shared
|
||||
endif
|
||||
CFLAGS := -g -Wall -fPIC -DSM64_LIB_EXPORT -DGBI_FLOATS
|
||||
CFLAGS := -g -Wall -fPIC -DSM64_LIB_EXPORT -DGBI_FLOATS -DVERSION_US
|
||||
|
||||
SRC_DIRS := src src/decomp src/decomp/engine src/decomp/game src/decomp/mario src/decomp/tools
|
||||
SRC_DIRS := src src/decomp src/decomp/engine src/decomp/include/PR src/decomp/game src/decomp/pc src/decomp/pc/audio src/decomp/mario src/decomp/tools src/decomp/audio
|
||||
BUILD_DIR := build
|
||||
DIST_DIR := dist
|
||||
ALL_DIRS := $(addprefix $(BUILD_DIR)/,$(SRC_DIRS))
|
||||
@@ -42,7 +42,7 @@ src/decomp/mario/geo.inc.c: ./import-mario-geo.py
|
||||
|
||||
$(BUILD_DIR)/%.o: %.c $(IMPORTED)
|
||||
@$(CC) $(CFLAGS) -MM -MP -MT $@ -MF $(BUILD_DIR)/$*.d $<
|
||||
$(CC) -c $(CFLAGS) -o $@ $<
|
||||
$(CC) -c $(CFLAGS) -I src/decomp/include -o $@ $<
|
||||
|
||||
$(LIB_FILE): $(O_FILES)
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
|
||||
@@ -807,16 +807,6 @@ void create_next_audio_buffer(s16 *samples, u32 num_samples) {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Called from threads: thread5_game_loop
|
||||
*/
|
||||
void play_sound(s32 soundBits, f32 *pos) {
|
||||
sSoundRequests[sSoundRequestCount].soundBits = soundBits;
|
||||
sSoundRequests[sSoundRequestCount].position = pos;
|
||||
sSoundRequestCount++;
|
||||
//DEBUG_PRINT("play_sound(%d) request#%d; pos %f %f %f\n", soundBits,sSoundRequestCount,pos[0],pos[1],pos[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from threads: thread4_sound, thread5_game_loop (EU only)
|
||||
*/
|
||||
|
||||
@@ -28,7 +28,6 @@ extern u8 gAudioSPTaskYieldBuffer[]; // ucode yield data ptr; only used in JP
|
||||
|
||||
struct SPTask *create_next_audio_frame_task(void);
|
||||
void create_next_audio_buffer(s16 *samples, u32 num_samples);
|
||||
void play_sound(s32 soundBits, f32 *pos);
|
||||
void audio_signal_game_loop_tick(void);
|
||||
void seq_player_fade_out(u8 player, u16 fadeDuration);
|
||||
void fade_volume_scale(u8 player, u8 targetScale, u16 fadeDuration);
|
||||
|
||||
@@ -76,6 +76,8 @@ struct PatchStruct {
|
||||
extern struct UnkStructSH8034EC88 D_SH_8034EC88[0x80];
|
||||
#endif
|
||||
|
||||
struct AudioBank *bank_load_immediate(s32 bankId, s32 arg1);
|
||||
|
||||
void audio_dma_partial_copy_async(uintptr_t *devAddr, u8 **vAddr, ssize_t *remaining, OSMesgQueue *queue, OSIoMesg *mesg);
|
||||
void decrease_sample_dma_ttls(void);
|
||||
#ifdef VERSION_SH
|
||||
|
||||
@@ -21,21 +21,56 @@
|
||||
#define SOUNDARGS_SHIFT_SOUNDID 16
|
||||
#define SOUNDARGS_SHIFT_PRIORITY 8
|
||||
|
||||
/* Sound banks */
|
||||
#define SOUND_BANK_ACTION 0
|
||||
#define SOUND_BANK_MOVING 1
|
||||
#define SOUND_BANK_VOICE 2
|
||||
#define SOUND_BANK_GENERAL 3
|
||||
#define SOUND_BANK_ENV 4
|
||||
#define SOUND_BANK_OBJ 5
|
||||
#define SOUND_BANK_AIR 6
|
||||
#define SOUND_BANK_MENU 7
|
||||
#define SOUND_BANK_GENERAL2 8
|
||||
#define SOUND_BANK_OBJ2 9
|
||||
#define SOUND_BANK_COUNT 10
|
||||
|
||||
#define SOUND_BANKS_ALL_BITS 0xffff
|
||||
#define SOUND_BANKS_ALL ((1 << SOUND_BANK_COUNT) - 1)
|
||||
#define SOUND_BANKS_FOREGROUND (\
|
||||
(1 << SOUND_BANK_ACTION) |\
|
||||
(1 << SOUND_BANK_VOICE) |\
|
||||
(1 << SOUND_BANK_MENU))
|
||||
#define SOUND_BANKS_BACKGROUND (SOUND_BANKS_ALL & ~SOUND_BANKS_FOREGROUND)
|
||||
#define SOUND_BANKS_DISABLED_DURING_INTRO_CUTSCENE (\
|
||||
(1 << SOUND_BANK_ENV) |\
|
||||
(1 << SOUND_BANK_OBJ) |\
|
||||
(1 << SOUND_BANK_GENERAL2) |\
|
||||
(1 << SOUND_BANK_OBJ2))
|
||||
#define SOUND_BANKS_DISABLED_AFTER_CREDITS (\
|
||||
(1 << SOUND_BANK_ACTION) |\
|
||||
(1 << SOUND_BANK_MOVING) |\
|
||||
(1 << SOUND_BANK_VOICE) |\
|
||||
(1 << SOUND_BANK_GENERAL))
|
||||
|
||||
/* Audio Status */
|
||||
#define SOUND_STATUS_STOPPED 0
|
||||
#define SOUND_STATUS_STARTING 1
|
||||
#define SOUND_STATUS_WAITING SOUND_STATUS_STARTING
|
||||
#define SOUND_STATUS_PLAYING 2
|
||||
|
||||
/* Audio lower bitflags. TODO: Figure out what these mean and use them below. */
|
||||
#define SOUND_LO_BITFLAG_UNK1 0x10 // fade in?
|
||||
#define SOUND_LOWER_BACKGROUND_MUSIC SOUND_LO_BITFLAG_UNK1
|
||||
#define SOUND_NO_ECHO 0x20 // not in JP
|
||||
#define SOUND_LO_BITFLAG_UNK8 0x80 // restart playing on each play_sound call?
|
||||
#define SOUND_DISCRETE SOUND_LO_BITFLAG_UNK8
|
||||
|
||||
/* Audio playback bitflags. */
|
||||
#define SOUND_NO_VOLUME_LOSS 0x1000000 // No volume loss with distance
|
||||
#define SOUND_VIBRATO 0x2000000 // Randomly alter frequency each audio frame
|
||||
#define SOUND_NO_PRIORITY_LOSS 0x4000000 // Do not prioritize closer sounds
|
||||
#define SOUND_NO_FREQUENCY_LOSS 0x8000000 // Frequency scale does not change with distance
|
||||
#define SOUND_CONSTANT_FREQUENCY SOUND_NO_FREQUENCY_LOSS
|
||||
|
||||
// silence
|
||||
#define NO_SOUND 0
|
||||
@@ -558,4 +593,4 @@
|
||||
#define SOUND_OBJ2_BOSS_DIALOG_GRUNT SOUND_ARG_LOAD(9, 0, 0x69, 0x40, 8)
|
||||
#define SOUND_OBJ2_MRI_SPINNING SOUND_ARG_LOAD(9, 0, 0x6B, 0x00, 8)
|
||||
|
||||
#endif // AUDIO_DEFINES_H
|
||||
#endif // AUDIO_DEFINES_H
|
||||
+14
-14
@@ -4,7 +4,7 @@
|
||||
// This file contains various data types used in Super Mario 64 that don't yet
|
||||
// have an appropriate header.
|
||||
|
||||
// #include <ultra64.h>
|
||||
#include "ultra64.h"
|
||||
#include "macros.h"
|
||||
#include "PR/ultratypes.h"
|
||||
|
||||
@@ -62,19 +62,19 @@ enum SpTaskState {
|
||||
SPTASK_STATE_FINISHED_DP
|
||||
};
|
||||
|
||||
// struct SPTask
|
||||
// {
|
||||
// /*0x00*/ OSTask task;
|
||||
// /*0x40*/ OSMesgQueue *msgqueue;
|
||||
// /*0x44*/ OSMesg msg;
|
||||
// /*0x48*/ enum SpTaskState state;
|
||||
// }; // size = 0x4C, align = 0x8
|
||||
//
|
||||
// struct VblankHandler
|
||||
// {
|
||||
// OSMesgQueue *queue;
|
||||
// OSMesg msg;
|
||||
// };
|
||||
struct SPTask
|
||||
{
|
||||
/*0x00*/ OSTask task;
|
||||
/*0x40*/ OSMesgQueue *msgqueue;
|
||||
/*0x44*/ OSMesg msg;
|
||||
/*0x48*/ enum SpTaskState state;
|
||||
}; // size = 0x4C, align = 0x8
|
||||
|
||||
struct VblankHandler
|
||||
{
|
||||
OSMesgQueue *queue;
|
||||
OSMesg msg;
|
||||
};
|
||||
|
||||
#define ANIM_FLAG_NOLOOP (1 << 0) // 0x01
|
||||
#define ANIM_FLAG_FORWARD (1 << 1) // 0x02
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "libultra_internal.h"
|
||||
#include "libaudio_internal.h"
|
||||
|
||||
#define PATCH(SRC, BASE, TYPE) //SRC = (TYPE)((uintptr_t) SRC + (uintptr_t) BASE)
|
||||
|
||||
void alSeqFileNew(ALSeqFile *f, u8 *base) {
|
||||
int i;
|
||||
for (i = 0; i < f->seqCount; i++) {
|
||||
PATCH(f->seqArray[i].offset, base, u8 *);
|
||||
}
|
||||
}
|
||||
|
||||
static void _bnkfPatchBank(ALInstrument *inst, ALBankFile *f, u8 *table) {
|
||||
int i;
|
||||
ALSound *sound;
|
||||
ALWaveTable *wavetable;
|
||||
u8 *table2;
|
||||
|
||||
if (inst->flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
inst->flags = 1;
|
||||
|
||||
for (i = 0; i < inst->soundCount; i++) {
|
||||
PATCH(inst->soundArray[i], f, ALSound *);
|
||||
sound = inst->soundArray[i];
|
||||
if (sound->flags) {
|
||||
continue;
|
||||
}
|
||||
|
||||
table2 = table;
|
||||
|
||||
sound->flags = 1;
|
||||
PATCH(sound->envelope, f, ALEnvelope *);
|
||||
PATCH(sound->keyMap, f, ALKeyMap *);
|
||||
PATCH(sound->wavetable, f, ALWaveTable *);
|
||||
wavetable = sound->wavetable;
|
||||
if (wavetable->flags) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wavetable->flags = 1;
|
||||
PATCH(wavetable->base, table2, u8 *);
|
||||
if (wavetable->type == 0) {
|
||||
PATCH(wavetable->waveInfo.adpcmWave.book, f, ALADPCMBook *);
|
||||
if (wavetable->waveInfo.adpcmWave.loop != NULL) {
|
||||
PATCH(wavetable->waveInfo.adpcmWave.loop, f, ALADPCMloop *);
|
||||
}
|
||||
} else if (wavetable->type == 1) {
|
||||
if (wavetable->waveInfo.rawWave.loop != NULL) {
|
||||
PATCH(wavetable->waveInfo.rawWave.loop, f, ALRawLoop *);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Force adding another jr $ra. Has to be called or it doesn't get put in the
|
||||
// right place.
|
||||
static void unused(void) {
|
||||
}
|
||||
|
||||
void alBnkfNew(ALBankFile *f, u8 *table) {
|
||||
ALBank *bank;
|
||||
int i;
|
||||
int j;
|
||||
unused();
|
||||
if (f->revision != AL_BANK_VERSION) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < f->bankCount; i++) {
|
||||
PATCH(f->bankArray[i], f, ALBank *);
|
||||
if (f->bankArray[i] == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bank = f->bankArray[i];
|
||||
if (bank->flags == 0) {
|
||||
bank->flags = 1;
|
||||
if (bank->percussion != NULL) {
|
||||
PATCH(bank->percussion, f, ALInstrument *);
|
||||
_bnkfPatchBank(bank->percussion, f, table);
|
||||
}
|
||||
for (j = 0; j < bank->instCount; j++) {
|
||||
PATCH(bank->instArray[j], f, ALInstrument *);
|
||||
if (bank->instArray[j] != NULL) {
|
||||
_bnkfPatchBank(bank->instArray[j], f, table);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "../compat.h"
|
||||
|
||||
#if (defined(__linux__) || defined(__BSD__)) && !defined(TARGET_WEB)
|
||||
/*
|
||||
Simple sound playback using ALSA API and libasound.
|
||||
Dependencies: libasound, alsa
|
||||
Build-Dependencies: liasound-dev
|
||||
Compile: gcc -lasound -o play sound_playback.c
|
||||
Usage: ./play <sample_rate> <channels> < <file>
|
||||
Examples:
|
||||
./play 44100 2 5 < /dev/urandom
|
||||
./play 22050 1 8 < /path/to/file.wav
|
||||
|
||||
Copyright (C) 2009 Alessandro Ghedini <al3xbio@gmail.com>
|
||||
--------------------------------------------------------------
|
||||
"THE BEER-WARE LICENSE" (Revision 42):
|
||||
Alessandro Ghedini wrote this file. As long as you retain this
|
||||
notice you can do whatever you want with this stuff. If we
|
||||
meet some day, and you think this stuff is worth it, you can
|
||||
buy me a beer in return.
|
||||
--------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "audio_api.h"
|
||||
|
||||
#define PCM_DEVICE "default"
|
||||
static snd_pcm_t *pcm_handle;
|
||||
static unsigned long int alsa_buffer_size;
|
||||
|
||||
static unsigned long get_time(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (unsigned long)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
|
||||
}
|
||||
|
||||
static bool audio_alsa_init(void) {
|
||||
int pcm;
|
||||
unsigned int tmp;
|
||||
unsigned int rate, channels;
|
||||
snd_pcm_hw_params_t *params;
|
||||
snd_pcm_uframes_t frames;
|
||||
|
||||
rate = 32000;
|
||||
channels = 2;
|
||||
|
||||
/* Open the PCM device in playback mode */
|
||||
if ((pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE,
|
||||
SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
|
||||
printf("ERROR: Can't open \"%s\" PCM device. %s\n",
|
||||
PCM_DEVICE, snd_strerror(pcm));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Allocate parameters object and fill it with default values*/
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
|
||||
snd_pcm_hw_params_any(pcm_handle, params);
|
||||
|
||||
/* Set parameters */
|
||||
if ((pcm = snd_pcm_hw_params_set_access(pcm_handle, params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm));
|
||||
|
||||
if ((pcm = snd_pcm_hw_params_set_format(pcm_handle, params,
|
||||
SND_PCM_FORMAT_S16_LE)) < 0)
|
||||
printf("ERROR: Can't set format. %s\n", snd_strerror(pcm));
|
||||
|
||||
if ((pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, channels)) < 0)
|
||||
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
|
||||
|
||||
if ((pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0)) < 0)
|
||||
printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm));
|
||||
|
||||
alsa_buffer_size = 1600 + 528 + 544; // five audio buffers from the game
|
||||
if ((pcm = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, params, &alsa_buffer_size)) < 0)
|
||||
printf("ERROR: Can't set buffer size. %s\n", snd_strerror(pcm));
|
||||
|
||||
/* Write parameters */
|
||||
if ((pcm = snd_pcm_hw_params(pcm_handle, params)) < 0)
|
||||
printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));
|
||||
|
||||
/* Resume information */
|
||||
printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle));
|
||||
|
||||
printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
|
||||
|
||||
snd_pcm_hw_params_get_channels(params, &tmp);
|
||||
printf("channels: %i ", tmp);
|
||||
|
||||
if (tmp == 1)
|
||||
printf("(mono)\n");
|
||||
else if (tmp == 2)
|
||||
printf("(stereo)\n");
|
||||
|
||||
snd_pcm_hw_params_get_rate(params, &tmp, 0);
|
||||
printf("rate: %d bps\n", tmp);
|
||||
|
||||
snd_pcm_hw_params_get_buffer_size(params, &alsa_buffer_size);
|
||||
printf("buffer size: %lu\n", alsa_buffer_size);
|
||||
|
||||
/* Allocate buffer to hold single period */
|
||||
snd_pcm_hw_params_get_period_size(params, &frames, 0);
|
||||
printf("frames: %lu\n", frames);
|
||||
|
||||
snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
|
||||
printf("time: %d\n", tmp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int audio_alsa_buffered(void) {
|
||||
if (!pcm_handle) {
|
||||
return 0;
|
||||
}
|
||||
snd_pcm_sframes_t ret = snd_pcm_avail(pcm_handle);
|
||||
if (ret < 0) {
|
||||
return 0;
|
||||
}
|
||||
ret = alsa_buffer_size - ret;
|
||||
return ret < 0 ? 0 : ret;
|
||||
}
|
||||
|
||||
static int audio_alsa_get_desired_buffered(void) {
|
||||
return 1100;
|
||||
}
|
||||
|
||||
static void audio_alsa_play(const uint8_t* buff, size_t len) {
|
||||
if (!pcm_handle) {
|
||||
audio_alsa_init();
|
||||
}
|
||||
//unsigned long t1 = get_time();
|
||||
int frames = len / 4;
|
||||
int pcm;
|
||||
if ((pcm = snd_pcm_writei(pcm_handle, buff, frames)) == -EPIPE) {
|
||||
printf("XRUN.\n");
|
||||
snd_pcm_prepare(pcm_handle);
|
||||
// Add some silence to avoid another XRUN
|
||||
char buf[1100 * 4 + len];
|
||||
memset(buf, 0, 1100 * 4);
|
||||
memcpy(buf + 1100 * 4, buff, len);
|
||||
if ((pcm = snd_pcm_writei(pcm_handle, buf, 1100 + frames)) < 0) {
|
||||
printf("Failed again %d\n", pcm);
|
||||
}
|
||||
} else if (pcm < 0) {
|
||||
printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm));
|
||||
return;
|
||||
}
|
||||
//fprintf(stderr, "%u ", get_time() - t1);
|
||||
}
|
||||
|
||||
struct AudioAPI audio_alsa = {
|
||||
audio_alsa_init,
|
||||
audio_alsa_buffered,
|
||||
audio_alsa_get_desired_buffered,
|
||||
audio_alsa_play
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef AUDIO_ALSA_H
|
||||
#define AUDIO_ALSA_H
|
||||
|
||||
#include "../compat.h"
|
||||
|
||||
#if defined(__linux__) || defined(__BSD__)
|
||||
extern struct AudioAPI audio_alsa;
|
||||
#define HAVE_ALSA 1
|
||||
#else
|
||||
#define HAVE_ALSA 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef AUDIO_API_H
|
||||
#define AUDIO_API_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct AudioAPI {
|
||||
bool (*init)(void);
|
||||
int (*buffered)(void);
|
||||
int (*get_desired_buffered)(void);
|
||||
void (*play)(const uint8_t *buf, size_t len);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <macros.h>
|
||||
#include "audio_api.h"
|
||||
|
||||
static bool audio_null_init(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static int audio_null_buffered(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int audio_null_get_desired_buffered(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void audio_null_play(UNUSED const uint8_t *buf, UNUSED size_t len) {
|
||||
}
|
||||
|
||||
struct AudioAPI audio_null = {
|
||||
audio_null_init,
|
||||
audio_null_buffered,
|
||||
audio_null_get_desired_buffered,
|
||||
audio_null_play
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef AUDIO_NULL_H
|
||||
#define AUDIO_NULL_H
|
||||
|
||||
#include "audio_api.h"
|
||||
|
||||
extern struct AudioAPI audio_null;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "../compat.h"
|
||||
|
||||
#if defined(__linux__) || defined(__BSD__)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include "macros.h"
|
||||
#include "audio_api.h"
|
||||
|
||||
static struct {
|
||||
pa_mainloop *mainloop;
|
||||
pa_context *context;
|
||||
pa_stream *stream;
|
||||
pa_buffer_attr attr;
|
||||
bool write_complete;
|
||||
} pas;
|
||||
|
||||
static void pas_context_state_cb(pa_context *c, void *userdata) {
|
||||
switch (pa_context_get_state(c)) {
|
||||
case PA_CONTEXT_READY:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_FAILED:
|
||||
*((bool *)userdata) = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pas_stream_state_cb(pa_stream *s, void *userdata) {
|
||||
switch (pa_stream_get_state(s)) {
|
||||
case PA_STREAM_READY:
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
*((bool *)userdata) = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pas_stream_write_cb(UNUSED pa_stream *s, UNUSED size_t length, UNUSED void *userdata) {
|
||||
//size_t ws = pa_stream_writable_size(pas.stream);
|
||||
//printf("write cb: %d %d\n", (int)length, (int)ws);
|
||||
}
|
||||
|
||||
static bool audio_pulse_init(void) {
|
||||
// Create mainloop
|
||||
pas.mainloop = pa_mainloop_new();
|
||||
if (pas.mainloop == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create context and connect
|
||||
pas.context = pa_context_new(pa_mainloop_get_api(pas.mainloop), "Super Mario 64");
|
||||
if (pas.context == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
pa_context_set_state_callback(pas.context, pas_context_state_cb, &done);
|
||||
|
||||
if (pa_context_connect(pas.context, NULL, 0, NULL) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
while (!done) {
|
||||
pa_mainloop_iterate(pas.mainloop, true, NULL);
|
||||
}
|
||||
pa_context_set_state_callback(pas.context, NULL, NULL);
|
||||
if (pa_context_get_state(pas.context) != PA_CONTEXT_READY) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Create stream
|
||||
pa_sample_spec ss;
|
||||
ss.format = PA_SAMPLE_S16LE;
|
||||
ss.rate = 32000;
|
||||
ss.channels = 2;
|
||||
|
||||
pa_buffer_attr attr;
|
||||
attr.maxlength = (1600 + 544 + 528 + 1600) * 4;
|
||||
attr.tlength = (528*2 + 544) * 4;
|
||||
attr.prebuf = 1500 * 4;
|
||||
attr.minreq = 161 * 4;
|
||||
attr.fragsize = (uint32_t)-1;
|
||||
|
||||
pas.stream = pa_stream_new(pas.context, "mario", &ss, NULL);
|
||||
if (pas.stream == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
done = false;
|
||||
pa_stream_set_state_callback(pas.stream, pas_stream_state_cb, &done);
|
||||
pa_stream_set_write_callback(pas.stream, pas_stream_write_cb, NULL);
|
||||
if (pa_stream_connect_playback(pas.stream, NULL, &attr, PA_STREAM_ADJUST_LATENCY, NULL, NULL) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
while (!done) {
|
||||
pa_mainloop_iterate(pas.mainloop, true, NULL);
|
||||
}
|
||||
pa_stream_set_state_callback(pas.stream, NULL, NULL);
|
||||
if (pa_stream_get_state(pas.stream) != PA_STREAM_READY) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
const pa_buffer_attr *applied_attr = pa_stream_get_buffer_attr(pas.stream);
|
||||
printf("maxlength: %u\ntlength: %u\nprebuf: %u\nminreq: %u\nfragsize: %u\n",
|
||||
applied_attr->maxlength, applied_attr->tlength, applied_attr->prebuf, applied_attr->minreq, applied_attr->fragsize);
|
||||
pas.attr = *applied_attr;
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
if (pas.stream != NULL) {
|
||||
pa_stream_unref(pas.stream);
|
||||
pas.stream = NULL;
|
||||
}
|
||||
if (pas.context != NULL) {
|
||||
pa_context_disconnect(pas.context);
|
||||
pa_context_unref(pas.context);
|
||||
pas.context = NULL;
|
||||
}
|
||||
if (pas.mainloop != NULL) {
|
||||
pa_mainloop_free(pas.mainloop);
|
||||
pas.mainloop = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void pas_update_complete(UNUSED pa_stream *stream, UNUSED int success, void *userdata) {
|
||||
*(bool *)userdata = true;
|
||||
}
|
||||
|
||||
static void pas_update(void) {
|
||||
bool done = false;
|
||||
pa_stream_update_timing_info(pas.stream, pas_update_complete, &done);
|
||||
while (!done) {
|
||||
pa_mainloop_iterate(pas.mainloop, true, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void pas_write_complete(UNUSED void *p) {
|
||||
pas.write_complete = true;
|
||||
}
|
||||
|
||||
static int audio_pulse_buffered(void) {
|
||||
if (pas.stream == NULL) {
|
||||
return 0;
|
||||
}
|
||||
pas_update();
|
||||
const pa_timing_info *info = pa_stream_get_timing_info(pas.stream);
|
||||
if (info == NULL) {
|
||||
printf("pa_stream_get_timing_info failed, state is %d\n", pa_stream_get_state(pas.stream));
|
||||
}
|
||||
/*int diff = info->write_index - info->read_index + (int)(info->sink_usec * 0.128);
|
||||
pa_usec_t usec;
|
||||
pa_stream_get_time(pas.stream, &usec);
|
||||
int32_t t = info->timestamp.tv_sec * 1000000 + info->timestamp.tv_usec - (usec - info->transport_usec);
|
||||
static int t0;
|
||||
if (t0 == 0) t0 = t;*/
|
||||
|
||||
/*int r = pa_mainloop_iterate(pas.mainloop, false, NULL);
|
||||
size_t ws = pa_stream_writable_size(pas.stream);
|
||||
printf("Writable: %d (%d) %d %d %d %d %llu %d\n", (int)ws, r, (int)(info->write_index - info->read_index), diff, (int)info->sink_usec, (int)info->transport_usec, (unsigned long long)usec, t - t0);*/
|
||||
//return (pas.attr.tlength - ws) / 4;
|
||||
return (info->write_index - info->read_index) / 4;
|
||||
}
|
||||
|
||||
static int audio_pulse_get_desired_buffered(void) {
|
||||
return 1100;
|
||||
}
|
||||
|
||||
static void audio_pulse_play(const uint8_t *buf, size_t len) {
|
||||
if (pas.stream == NULL) {
|
||||
if (!audio_pulse_init()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
//size_t ws = pa_stream_writable_size(pas.stream);
|
||||
size_t ws = pas.attr.maxlength - audio_pulse_buffered() * 4;
|
||||
if (ws < len) {
|
||||
//printf("Warning: can't write everything: %d vs %d\n", (int)len, (int)ws);
|
||||
len = ws;
|
||||
}
|
||||
if (pa_stream_write(pas.stream, buf, len, pas_write_complete, 0LL, PA_SEEK_RELATIVE) < 0) {
|
||||
printf("pa_stream_write failed\n");
|
||||
return;
|
||||
}
|
||||
while (!pas.write_complete) {
|
||||
pa_mainloop_iterate(pas.mainloop, true, NULL);
|
||||
}
|
||||
pas.write_complete = false;
|
||||
}
|
||||
|
||||
struct AudioAPI audio_pulse = {
|
||||
audio_pulse_init,
|
||||
audio_pulse_buffered,
|
||||
audio_pulse_get_desired_buffered,
|
||||
audio_pulse_play
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef AUDIO_PULSE_H
|
||||
#define AUDIO_PULSE_H
|
||||
|
||||
#include "../compat.h"
|
||||
|
||||
#if defined(__linux__) || defined(__BSD__)
|
||||
extern struct AudioAPI audio_pulse;
|
||||
#define HAVE_PULSE_AUDIO 1
|
||||
#else
|
||||
#define HAVE_PULSE_AUDIO 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,219 @@
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
#include <objbase.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <audioclient.h>
|
||||
|
||||
#include "audio_api.h"
|
||||
|
||||
#include "../../../debug_print.h"
|
||||
|
||||
// These constants are currently missing from the MinGW headers.
|
||||
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
|
||||
# define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
|
||||
#endif
|
||||
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
|
||||
# define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
|
||||
#endif
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
||||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||||
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
|
||||
|
||||
static ComPtr<IMMDeviceEnumerator> immdev_enumerator;
|
||||
|
||||
static struct WasapiState {
|
||||
ComPtr<IMMDevice> device;
|
||||
ComPtr<IAudioClient> client;
|
||||
ComPtr<IAudioRenderClient> rclient;
|
||||
UINT32 buffer_frame_count;
|
||||
bool initialized;
|
||||
bool started;
|
||||
} wasapi;
|
||||
|
||||
static class NotificationClient : public IMMNotificationClient {
|
||||
LONG refcount;
|
||||
public:
|
||||
NotificationClient() : refcount(1) {
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) {
|
||||
if (flow == eRender && role == eConsole) {
|
||||
// This callback runs on a separate thread,
|
||||
// but it's not important how fast this write takes effect.
|
||||
wasapi.initialized = false;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() {
|
||||
return InterlockedIncrement(&refcount);
|
||||
}
|
||||
|
||||
virtual ULONG STDMETHODCALLTYPE Release() {
|
||||
ULONG rc = InterlockedDecrement(&refcount);
|
||||
if (rc == 0) {
|
||||
delete this;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) {
|
||||
if (riid == __uuidof(IUnknown)) {
|
||||
AddRef();
|
||||
*ppvInterface = (IUnknown *)this;
|
||||
} else if (riid == __uuidof(IMMNotificationClient)) {
|
||||
AddRef();
|
||||
*ppvInterface = (IMMNotificationClient *)this;
|
||||
} else {
|
||||
*ppvInterface = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
} notification_client;
|
||||
|
||||
static void ThrowIfFailed(HRESULT res) {
|
||||
if (FAILED(res)) {
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
bool audio_wasapi_init(void) {
|
||||
CoInitialize(NULL);
|
||||
//DEBUG_PRINT("audio init!!!");
|
||||
try {
|
||||
ThrowIfFailed(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&immdev_enumerator)));
|
||||
} catch (HRESULT res) {
|
||||
//DEBUG_PRINT("audio init fail!!! %d", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
ThrowIfFailed(immdev_enumerator->RegisterEndpointNotificationCallback(new NotificationClient()));
|
||||
//DEBUG_PRINT("audio init OK!!!");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool audio_wasapi_setup_stream(void) {
|
||||
//DEBUG_PRINT("audio audio_wasapi_setup_stream!!!");
|
||||
wasapi = WasapiState();
|
||||
|
||||
try {
|
||||
ThrowIfFailed(immdev_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &wasapi.device));
|
||||
ThrowIfFailed(wasapi.device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, IID_PPV_ARGS_Helper(&wasapi.client)));
|
||||
|
||||
WAVEFORMATEX desired;
|
||||
desired.wFormatTag = WAVE_FORMAT_PCM;
|
||||
desired.nChannels = 2;
|
||||
desired.nSamplesPerSec = 32000;
|
||||
desired.nAvgBytesPerSec = 32000 * 2 * 2;
|
||||
desired.nBlockAlign = 4;
|
||||
desired.wBitsPerSample = 16;
|
||||
desired.cbSize = 0;
|
||||
|
||||
ThrowIfFailed(wasapi.client->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, 2000000, 0, &desired, nullptr));
|
||||
|
||||
ThrowIfFailed(wasapi.client->GetBufferSize(&wasapi.buffer_frame_count));
|
||||
ThrowIfFailed(wasapi.client->GetService(IID_PPV_ARGS(&wasapi.rclient)));
|
||||
|
||||
wasapi.started = false;
|
||||
wasapi.initialized = true;
|
||||
} catch (HRESULT res) {
|
||||
wasapi = WasapiState();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int audio_wasapi_buffered(void) {
|
||||
//DEBUG_PRINT("audio audio_wasapi_buffered!!!");
|
||||
if (!wasapi.initialized) {
|
||||
if (!audio_wasapi_setup_stream()) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
try {
|
||||
UINT32 padding;
|
||||
ThrowIfFailed(wasapi.client->GetCurrentPadding(&padding));
|
||||
return padding;
|
||||
} catch (HRESULT res) {
|
||||
wasapi = WasapiState();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int audio_wasapi_get_desired_buffered(void) {
|
||||
//DEBUG_PRINT("audio audio_wasapi_get_desired_buffered!!!");
|
||||
return 1100;
|
||||
}
|
||||
|
||||
//#include <stdio.h>
|
||||
static void audio_wasapi_play(const uint8_t *buf, size_t len) {
|
||||
//DEBUG_PRINT("audio audio_wasapi_play!!!");
|
||||
if (!wasapi.initialized) {
|
||||
if (!audio_wasapi_setup_stream()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
UINT32 frames = len / 4;
|
||||
|
||||
UINT32 padding;
|
||||
ThrowIfFailed(wasapi.client->GetCurrentPadding(&padding));
|
||||
if(padding >= 5400)
|
||||
return;
|
||||
//printf("%u %u\n", frames, padding);
|
||||
|
||||
UINT32 available = wasapi.buffer_frame_count - padding;
|
||||
if (available < frames) {
|
||||
frames = available;
|
||||
|
||||
}
|
||||
|
||||
BYTE *data;
|
||||
ThrowIfFailed(wasapi.rclient->GetBuffer(frames, &data));
|
||||
memcpy(data, buf, frames * 4);
|
||||
ThrowIfFailed(wasapi.rclient->ReleaseBuffer(frames, 0));
|
||||
|
||||
if (!wasapi.started && padding + frames > 1500) {
|
||||
wasapi.started = true;
|
||||
ThrowIfFailed(wasapi.client->Start());
|
||||
}
|
||||
} catch (HRESULT res) {
|
||||
wasapi = WasapiState();
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioAPI audio_wasapi = {
|
||||
audio_wasapi_init,
|
||||
audio_wasapi_buffered,
|
||||
audio_wasapi_get_desired_buffered,
|
||||
audio_wasapi_play
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef AUDIO_WASAPI_H
|
||||
#define AUDIO_WASAPI_H
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include "audio_api.h"
|
||||
extern struct AudioAPI audio_wasapi;
|
||||
#define HAVE_WASAPI 1
|
||||
#else
|
||||
#define HAVE_WASAPI 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef COMPAT_H
|
||||
#define COMPAT_H value
|
||||
|
||||
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
#define __BSD__
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,236 @@
|
||||
// configfile.c - handles loading and saving the configuration options
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "configfile.h"
|
||||
|
||||
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0]))
|
||||
|
||||
enum ConfigOptionType {
|
||||
CONFIG_TYPE_BOOL,
|
||||
CONFIG_TYPE_UINT,
|
||||
CONFIG_TYPE_FLOAT,
|
||||
};
|
||||
|
||||
struct ConfigOption {
|
||||
const char *name;
|
||||
enum ConfigOptionType type;
|
||||
union {
|
||||
bool *boolValue;
|
||||
unsigned int *uintValue;
|
||||
float *floatValue;
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
*Config options and default values
|
||||
*/
|
||||
bool configFullscreen = false;
|
||||
// Keyboard mappings (scancode values)
|
||||
unsigned int configKeyA = 0x26;
|
||||
unsigned int configKeyB = 0x33;
|
||||
unsigned int configKeyStart = 0x39;
|
||||
unsigned int configKeyR = 0x36;
|
||||
unsigned int configKeyZ = 0x25;
|
||||
unsigned int configKeyCUp = 0x148;
|
||||
unsigned int configKeyCDown = 0x150;
|
||||
unsigned int configKeyCLeft = 0x14B;
|
||||
unsigned int configKeyCRight = 0x14D;
|
||||
unsigned int configKeyStickUp = 0x11;
|
||||
unsigned int configKeyStickDown = 0x1F;
|
||||
unsigned int configKeyStickLeft = 0x1E;
|
||||
unsigned int configKeyStickRight = 0x20;
|
||||
|
||||
|
||||
static const struct ConfigOption options[] = {
|
||||
{.name = "fullscreen", .type = CONFIG_TYPE_BOOL, .boolValue = &configFullscreen},
|
||||
{.name = "key_a", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyA},
|
||||
{.name = "key_b", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyB},
|
||||
{.name = "key_start", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStart},
|
||||
{.name = "key_r", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyR},
|
||||
{.name = "key_z", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyZ},
|
||||
{.name = "key_cup", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCUp},
|
||||
{.name = "key_cdown", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCDown},
|
||||
{.name = "key_cleft", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCLeft},
|
||||
{.name = "key_cright", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyCRight},
|
||||
{.name = "key_stickup", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickUp},
|
||||
{.name = "key_stickdown", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickDown},
|
||||
{.name = "key_stickleft", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickLeft},
|
||||
{.name = "key_stickright", .type = CONFIG_TYPE_UINT, .uintValue = &configKeyStickRight},
|
||||
};
|
||||
|
||||
// Reads an entire line from a file (excluding the newline character) and returns an allocated string
|
||||
// Returns NULL if no lines could be read from the file
|
||||
static char *read_file_line(FILE *file) {
|
||||
char *buffer;
|
||||
size_t bufferSize = 8;
|
||||
size_t offset = 0; // offset in buffer to write
|
||||
|
||||
buffer = malloc(bufferSize);
|
||||
while (1) {
|
||||
// Read a line from the file
|
||||
if (fgets(buffer + offset, bufferSize - offset, file) == NULL) {
|
||||
free(buffer);
|
||||
return NULL; // Nothing could be read.
|
||||
}
|
||||
offset = strlen(buffer);
|
||||
assert(offset > 0);
|
||||
|
||||
// If a newline was found, remove the trailing newline and exit
|
||||
if (buffer[offset - 1] == '\n') {
|
||||
buffer[offset - 1] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
if (feof(file)) // EOF was reached
|
||||
break;
|
||||
|
||||
// If no newline or EOF was reached, then the whole line wasn't read.
|
||||
bufferSize *= 2; // Increase buffer size
|
||||
buffer = realloc(buffer, bufferSize);
|
||||
assert(buffer != NULL);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Returns the position of the first non-whitespace character
|
||||
static char *skip_whitespace(char *str) {
|
||||
while (isspace(*str))
|
||||
str++;
|
||||
return str;
|
||||
}
|
||||
|
||||
// NULL-terminates the current whitespace-delimited word, and returns a pointer to the next word
|
||||
static char *word_split(char *str) {
|
||||
// Precondition: str must not point to whitespace
|
||||
assert(!isspace(*str));
|
||||
|
||||
// Find either the next whitespace char or end of string
|
||||
while (!isspace(*str) && *str != '\0')
|
||||
str++;
|
||||
if (*str == '\0') // End of string
|
||||
return str;
|
||||
|
||||
// Terminate current word
|
||||
*(str++) = '\0';
|
||||
|
||||
// Skip whitespace to next word
|
||||
return skip_whitespace(str);
|
||||
}
|
||||
|
||||
// Splits a string into words, and stores the words into the 'tokens' array
|
||||
// 'maxTokens' is the length of the 'tokens' array
|
||||
// Returns the number of tokens parsed
|
||||
static unsigned int tokenize_string(char *str, int maxTokens, char **tokens) {
|
||||
int count = 0;
|
||||
|
||||
str = skip_whitespace(str);
|
||||
while (str[0] != '\0' && count < maxTokens) {
|
||||
tokens[count] = str;
|
||||
str = word_split(str);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Loads the config file specified by 'filename'
|
||||
void configfile_load(const char *filename) {
|
||||
FILE *file;
|
||||
char *line;
|
||||
|
||||
printf("Loading configuration from '%s'\n", filename);
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (file == NULL) {
|
||||
// Create a new config file and save defaults
|
||||
printf("Config file '%s' not found. Creating it.\n", filename);
|
||||
configfile_save(filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through each line in the file
|
||||
while ((line = read_file_line(file)) != NULL) {
|
||||
char *p = line;
|
||||
char *tokens[2];
|
||||
int numTokens;
|
||||
|
||||
while (isspace(*p))
|
||||
p++;
|
||||
numTokens = tokenize_string(p, 2, tokens);
|
||||
if (numTokens != 0) {
|
||||
if (numTokens == 2) {
|
||||
const struct ConfigOption *option = NULL;
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_LEN(options); i++) {
|
||||
if (strcmp(tokens[0], options[i].name) == 0) {
|
||||
option = &options[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (option == NULL)
|
||||
printf("unknown option '%s'\n", tokens[0]);
|
||||
else {
|
||||
switch (option->type) {
|
||||
case CONFIG_TYPE_BOOL:
|
||||
if (strcmp(tokens[1], "true") == 0)
|
||||
*option->boolValue = true;
|
||||
else if (strcmp(tokens[1], "false") == 0)
|
||||
*option->boolValue = false;
|
||||
break;
|
||||
case CONFIG_TYPE_UINT:
|
||||
sscanf(tokens[1], "%u", option->uintValue);
|
||||
break;
|
||||
case CONFIG_TYPE_FLOAT:
|
||||
sscanf(tokens[1], "%f", option->floatValue);
|
||||
break;
|
||||
default:
|
||||
assert(0); // bad type
|
||||
}
|
||||
printf("option: '%s', value: '%s'\n", tokens[0], tokens[1]);
|
||||
}
|
||||
} else
|
||||
puts("error: expected value");
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// Writes the config file to 'filename'
|
||||
void configfile_save(const char *filename) {
|
||||
FILE *file;
|
||||
|
||||
printf("Saving configuration to '%s'\n", filename);
|
||||
|
||||
file = fopen(filename, "w");
|
||||
if (file == NULL) {
|
||||
// error
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < ARRAY_LEN(options); i++) {
|
||||
const struct ConfigOption *option = &options[i];
|
||||
|
||||
switch (option->type) {
|
||||
case CONFIG_TYPE_BOOL:
|
||||
fprintf(file, "%s %s\n", option->name, *option->boolValue ? "true" : "false");
|
||||
break;
|
||||
case CONFIG_TYPE_UINT:
|
||||
fprintf(file, "%s %u\n", option->name, *option->uintValue);
|
||||
break;
|
||||
case CONFIG_TYPE_FLOAT:
|
||||
fprintf(file, "%s %f\n", option->name, *option->floatValue);
|
||||
break;
|
||||
default:
|
||||
assert(0); // unknown type
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CONFIGFILE_H
|
||||
#define CONFIGFILE_H
|
||||
|
||||
extern bool configFullscreen;
|
||||
extern unsigned int configKeyA;
|
||||
extern unsigned int configKeyB;
|
||||
extern unsigned int configKeyStart;
|
||||
extern unsigned int configKeyR;
|
||||
extern unsigned int configKeyZ;
|
||||
extern unsigned int configKeyCUp;
|
||||
extern unsigned int configKeyCDown;
|
||||
extern unsigned int configKeyCLeft;
|
||||
extern unsigned int configKeyCRight;
|
||||
extern unsigned int configKeyStickUp;
|
||||
extern unsigned int configKeyStickDown;
|
||||
extern unsigned int configKeyStickLeft;
|
||||
extern unsigned int configKeyStickRight;
|
||||
|
||||
void configfile_load(const char *filename);
|
||||
void configfile_save(const char *filename);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef CONTROLLER_API
|
||||
#define CONTROLLER_API
|
||||
|
||||
#include <ultra64.h>
|
||||
|
||||
struct ControllerAPI {
|
||||
void (*init)(void);
|
||||
void (*read)(OSContPad *pad);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,151 @@
|
||||
#ifdef TARGET_WEB
|
||||
|
||||
#include <string.h>
|
||||
#include <emscripten/html5.h>
|
||||
#include "macros.h"
|
||||
#include "controller_keyboard.h"
|
||||
|
||||
static const struct {
|
||||
const char *code;
|
||||
int scancode;
|
||||
} keymap_browser[] = {
|
||||
{"Escape", 0x01},
|
||||
{"Digit1", 0x02 },
|
||||
{"Digit2", 0x03 },
|
||||
{"Digit3", 0x04 },
|
||||
{"Digit4", 0x05 },
|
||||
{"Digit5", 0x06 },
|
||||
{"Digit6", 0x07 },
|
||||
{"Digit7", 0x08 },
|
||||
{"Digit8", 0x09 },
|
||||
{"Digit9", 0x0a },
|
||||
{"Digit0", 0x0b },
|
||||
{"Minus", 0x0c },
|
||||
{"Equal", 0x0d },
|
||||
{"Backspace", 0x0e },
|
||||
{"Tab", 0x0f },
|
||||
{"KeyQ", 0x10 },
|
||||
{"KeyW", 0x11 },
|
||||
{"KeyE", 0x12 },
|
||||
{"KeyR", 0x13 },
|
||||
{"KeyT", 0x14 },
|
||||
{"KeyY", 0x15 },
|
||||
{"KeyU", 0x16 },
|
||||
{"KeyI", 0x17 },
|
||||
{"KeyO", 0x18 },
|
||||
{"KeyP", 0x19 },
|
||||
{"BracketLeft", 0x1a },
|
||||
{"BracketRight", 0x1b },
|
||||
{"Enter", 0x1c },
|
||||
{"ControlLeft", 0x1d },
|
||||
{"KeyA", 0x1e },
|
||||
{"KeyS", 0x1f },
|
||||
{"KeyD", 0x20 },
|
||||
{"KeyF", 0x21 },
|
||||
{"KeyG", 0x22 },
|
||||
{"KeyH", 0x23 },
|
||||
{"KeyJ", 0x24 },
|
||||
{"KeyK", 0x25 },
|
||||
{"KeyL", 0x26 },
|
||||
{"Semicolon", 0x27 },
|
||||
{"Quote", 0x28 },
|
||||
{"Backquote", 0x29 },
|
||||
{"ShiftLeft", 0x2a },
|
||||
{"Backslash", 0x2b },
|
||||
{"KeyZ", 0x2c },
|
||||
{"KeyX", 0x2d },
|
||||
{"KeyC", 0x2e },
|
||||
{"KeyV", 0x2f },
|
||||
{"KeyB", 0x30 },
|
||||
{"KeyN", 0x31 },
|
||||
{"KeyM", 0x32 },
|
||||
{"Comma", 0x33 },
|
||||
{"Period", 0x34 },
|
||||
{"Slash", 0x35 },
|
||||
{"ShiftRight", 0x36 },
|
||||
{"NumpadMultiply", 0x37 },
|
||||
{"AltLeft", 0x38 },
|
||||
{"Space", 0x39 },
|
||||
{"CapsLock", 0x3a },
|
||||
{"F1", 0x3b },
|
||||
{"F2", 0x3c },
|
||||
{"F3", 0x3d },
|
||||
{"F4", 0x3e },
|
||||
{"F5", 0x3f },
|
||||
{"F6", 0x40 },
|
||||
{"F7", 0x41 },
|
||||
{"F8", 0x42 },
|
||||
{"F9", 0x43 },
|
||||
{"F10", 0x44 },
|
||||
{"NumLock", 0x45 },
|
||||
{"ScrollLock", 0x46 },
|
||||
{"Numpad7", 0x47 },
|
||||
{"Numpad8", 0x48 },
|
||||
{"Numpad9", 0x49 },
|
||||
{"NumpadSubtract", 0x4a },
|
||||
{"Numpad4", 0x4b },
|
||||
{"Numpad5", 0x4c },
|
||||
{"Numpad6", 0x4d },
|
||||
{"NumpadAdd", 0x4e },
|
||||
{"Numpad1", 0x4f },
|
||||
{"Numpad2", 0x50 },
|
||||
{"Numpad3", 0x51 },
|
||||
{"Numpad0", 0x52 },
|
||||
{"NumpadDecimal", 0x53 },
|
||||
{"PrintScreen", 0x54 },
|
||||
// 0x55
|
||||
{"IntlBackslash", 0x56 },
|
||||
{"F11", 0x57 },
|
||||
{"F12", 0x58 },
|
||||
{"IntlRo", 0x59 },
|
||||
//{"Katakana", 0 },
|
||||
//{"Hiragana", 0 },
|
||||
{"NumpadEnter", 0x11c },
|
||||
{"ControlRight", 0x11d },
|
||||
{"NumpadDivide", 0x135 },
|
||||
{"AltRight", 0x138 },
|
||||
{"Home", 0x147 },
|
||||
{"ArrowUp", 0x148 },
|
||||
{"PageUp", 0x149 },
|
||||
{"ArrowLeft", 0x14b },
|
||||
{"ArrowRight", 0x14d },
|
||||
{"End", 0x14f },
|
||||
{"ArrowDown", 0x150 },
|
||||
{"PageDown", 0x151 },
|
||||
{"Insert", 0x152 },
|
||||
{"Delete", 0x153 },
|
||||
{"Pause", 0x21d },
|
||||
{"MetaLeft", 0x15b },
|
||||
{"MetaRight", 0x15c },
|
||||
{"ContextMenu", 0x15d },
|
||||
};
|
||||
|
||||
static EM_BOOL controller_emscripten_keyboard_handler(int event_type, const EmscriptenKeyboardEvent *key_event, UNUSED void *user_data) {
|
||||
for (size_t i = 0; i < sizeof(keymap_browser) / sizeof(keymap_browser[0]); i++) {
|
||||
if (strcmp(key_event->code, keymap_browser[i].code) == 0) {
|
||||
if (event_type == EMSCRIPTEN_EVENT_KEYDOWN) {
|
||||
return keyboard_on_key_down(keymap_browser[i].scancode);
|
||||
} else if (event_type == EMSCRIPTEN_EVENT_KEYUP) {
|
||||
return keyboard_on_key_up(keymap_browser[i].scancode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return EM_FALSE;
|
||||
}
|
||||
|
||||
static EM_BOOL controller_emscripten_keyboard_blur_handler(UNUSED int event_type, UNUSED const EmscriptenFocusEvent *focus_event, UNUSED void *user_data) {
|
||||
keyboard_on_all_keys_up();
|
||||
return EM_TRUE;
|
||||
}
|
||||
|
||||
void controller_emscripten_keyboard_init(void) {
|
||||
// Should be #window according to docs, but that crashes
|
||||
const char *target = EMSCRIPTEN_EVENT_TARGET_WINDOW;
|
||||
|
||||
emscripten_set_keydown_callback(target, NULL, EM_FALSE, controller_emscripten_keyboard_handler);
|
||||
emscripten_set_keyup_callback(target, NULL, EM_FALSE, controller_emscripten_keyboard_handler);
|
||||
emscripten_set_blur_callback(target, NULL, EM_FALSE, controller_emscripten_keyboard_blur_handler);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef CONTROLLER_KEYBOARD_EMSCRIPTEN_H
|
||||
#define CONTROLLER_KEYBOARD_EMSCRIPTEN_H
|
||||
|
||||
#ifdef TARGET_WEB
|
||||
void controller_emscripten_keyboard_init(void);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "macros.h"
|
||||
|
||||
#include "lib/src/libultra_internal.h"
|
||||
#include "lib/src/osContInternal.h"
|
||||
|
||||
#include "controller_recorded_tas.h"
|
||||
#include "controller_keyboard.h"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include "controller_xinput.h"
|
||||
#else
|
||||
#include "controller_sdl.h"
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include "controller_wup.h"
|
||||
#endif
|
||||
|
||||
static struct ControllerAPI *controller_implementations[] = {
|
||||
&controller_recorded_tas,
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
&controller_xinput,
|
||||
#else
|
||||
&controller_sdl,
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
&controller_wup,
|
||||
#endif
|
||||
&controller_keyboard,
|
||||
};
|
||||
|
||||
s32 osContInit(UNUSED OSMesgQueue *mq, u8 *controllerBits, UNUSED OSContStatus *status) {
|
||||
for (size_t i = 0; i < sizeof(controller_implementations) / sizeof(struct ControllerAPI *); i++) {
|
||||
controller_implementations[i]->init();
|
||||
}
|
||||
*controllerBits = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 osContStartReadData(UNUSED OSMesgQueue *mesg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osContGetReadData(OSContPad *pad) {
|
||||
pad->button = 0;
|
||||
pad->stick_x = 0;
|
||||
pad->stick_y = 0;
|
||||
pad->errnum = 0;
|
||||
|
||||
for (size_t i = 0; i < sizeof(controller_implementations) / sizeof(struct ControllerAPI *); i++) {
|
||||
controller_implementations[i]->read(pad);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <stdbool.h>
|
||||
#include <ultra64.h>
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
#ifdef TARGET_WEB
|
||||
#include "controller_emscripten_keyboard.h"
|
||||
#endif
|
||||
|
||||
#include "../configfile.h"
|
||||
|
||||
static int keyboard_buttons_down;
|
||||
|
||||
static int keyboard_mapping[13][2];
|
||||
|
||||
static int keyboard_map_scancode(int scancode) {
|
||||
int ret = 0;
|
||||
for (size_t i = 0; i < sizeof(keyboard_mapping) / sizeof(keyboard_mapping[0]); i++) {
|
||||
if (keyboard_mapping[i][0] == scancode) {
|
||||
ret |= keyboard_mapping[i][1];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool keyboard_on_key_down(int scancode) {
|
||||
int mapped = keyboard_map_scancode(scancode);
|
||||
keyboard_buttons_down |= mapped;
|
||||
return mapped != 0;
|
||||
}
|
||||
|
||||
bool keyboard_on_key_up(int scancode) {
|
||||
int mapped = keyboard_map_scancode(scancode);
|
||||
keyboard_buttons_down &= ~mapped;
|
||||
return mapped != 0;
|
||||
}
|
||||
|
||||
void keyboard_on_all_keys_up(void) {
|
||||
keyboard_buttons_down = 0;
|
||||
}
|
||||
|
||||
static void set_keyboard_mapping(int index, int mask, int scancode) {
|
||||
keyboard_mapping[index][0] = scancode;
|
||||
keyboard_mapping[index][1] = mask;
|
||||
}
|
||||
|
||||
static void keyboard_init(void) {
|
||||
int i = 0;
|
||||
|
||||
set_keyboard_mapping(i++, 0x80000, configKeyStickUp);
|
||||
set_keyboard_mapping(i++, 0x10000, configKeyStickLeft);
|
||||
set_keyboard_mapping(i++, 0x40000, configKeyStickDown);
|
||||
set_keyboard_mapping(i++, 0x20000, configKeyStickRight);
|
||||
set_keyboard_mapping(i++, A_BUTTON, configKeyA);
|
||||
set_keyboard_mapping(i++, B_BUTTON, configKeyB);
|
||||
set_keyboard_mapping(i++, Z_TRIG, configKeyZ);
|
||||
set_keyboard_mapping(i++, U_CBUTTONS, configKeyCUp);
|
||||
set_keyboard_mapping(i++, L_CBUTTONS, configKeyCLeft);
|
||||
set_keyboard_mapping(i++, D_CBUTTONS, configKeyCDown);
|
||||
set_keyboard_mapping(i++, R_CBUTTONS, configKeyCRight);
|
||||
set_keyboard_mapping(i++, R_TRIG, configKeyR);
|
||||
set_keyboard_mapping(i++, START_BUTTON, configKeyStart);
|
||||
|
||||
#ifdef TARGET_WEB
|
||||
controller_emscripten_keyboard_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void keyboard_read(OSContPad *pad) {
|
||||
pad->button |= keyboard_buttons_down;
|
||||
if ((keyboard_buttons_down & 0x30000) == 0x10000) {
|
||||
pad->stick_x = -128;
|
||||
}
|
||||
if ((keyboard_buttons_down & 0x30000) == 0x20000) {
|
||||
pad->stick_x = 127;
|
||||
}
|
||||
if ((keyboard_buttons_down & 0xc0000) == 0x40000) {
|
||||
pad->stick_y = -128;
|
||||
}
|
||||
if ((keyboard_buttons_down & 0xc0000) == 0x80000) {
|
||||
pad->stick_y = 127;
|
||||
}
|
||||
}
|
||||
|
||||
struct ControllerAPI controller_keyboard = {
|
||||
keyboard_init,
|
||||
keyboard_read
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef CONTROLLER_KEYBOARD_H
|
||||
#define CONTROLLER_KEYBOARD_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "controller_api.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
bool keyboard_on_key_down(int scancode);
|
||||
bool keyboard_on_key_up(int scancode);
|
||||
void keyboard_on_all_keys_up(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
extern struct ControllerAPI controller_keyboard;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <stdio.h>
|
||||
#include <ultra64.h>
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
static FILE *fp;
|
||||
|
||||
static void tas_init(void) {
|
||||
fp = fopen("cont.m64", "rb");
|
||||
if (fp != NULL) {
|
||||
uint8_t buf[0x400];
|
||||
fread(buf, 1, sizeof(buf), fp);
|
||||
}
|
||||
}
|
||||
|
||||
static void tas_read(OSContPad *pad) {
|
||||
if (fp != NULL) {
|
||||
uint8_t bytes[4] = {0};
|
||||
fread(bytes, 1, 4, fp);
|
||||
pad->button = (bytes[0] << 8) | bytes[1];
|
||||
pad->stick_x = bytes[2];
|
||||
pad->stick_y = bytes[3];
|
||||
}
|
||||
}
|
||||
|
||||
struct ControllerAPI controller_recorded_tas = {
|
||||
tas_init,
|
||||
tas_read
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef CONTROLLER_RECORDED_TAS_H
|
||||
#define CONTROLLER_RECORDED_TAS_H
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
extern struct ControllerAPI controller_recorded_tas;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <ultra64.h>
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
#define DEADZONE 4960
|
||||
|
||||
static bool init_ok;
|
||||
static SDL_GameController *sdl_cntrl;
|
||||
|
||||
static void controller_sdl_init(void) {
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) {
|
||||
fprintf(stderr, "SDL init error: %s\n", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
init_ok = true;
|
||||
}
|
||||
|
||||
static void controller_sdl_read(OSContPad *pad) {
|
||||
if (!init_ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GameControllerUpdate();
|
||||
|
||||
if (sdl_cntrl != NULL && !SDL_GameControllerGetAttached(sdl_cntrl)) {
|
||||
SDL_GameControllerClose(sdl_cntrl);
|
||||
sdl_cntrl = NULL;
|
||||
}
|
||||
if (sdl_cntrl == NULL) {
|
||||
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
||||
if (SDL_IsGameController(i)) {
|
||||
sdl_cntrl = SDL_GameControllerOpen(i);
|
||||
if (sdl_cntrl != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sdl_cntrl == NULL) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_GameControllerGetButton(sdl_cntrl, SDL_CONTROLLER_BUTTON_START)) pad->button |= START_BUTTON;
|
||||
if (SDL_GameControllerGetButton(sdl_cntrl, SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) pad->button |= Z_TRIG;
|
||||
if (SDL_GameControllerGetButton(sdl_cntrl, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) pad->button |= R_TRIG;
|
||||
if (SDL_GameControllerGetButton(sdl_cntrl, SDL_CONTROLLER_BUTTON_A)) pad->button |= A_BUTTON;
|
||||
if (SDL_GameControllerGetButton(sdl_cntrl, SDL_CONTROLLER_BUTTON_X)) pad->button |= B_BUTTON;
|
||||
|
||||
int16_t leftx = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_LEFTX);
|
||||
int16_t lefty = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_LEFTY);
|
||||
int16_t rightx = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||
int16_t righty = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||
|
||||
int16_t ltrig = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
||||
int16_t rtrig = SDL_GameControllerGetAxis(sdl_cntrl, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
||||
|
||||
#ifdef TARGET_WEB
|
||||
// Firefox has a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1606562
|
||||
// It sets down y to 32768.0f / 32767.0f, which is greater than the allowed 1.0f,
|
||||
// which SDL then converts to a int16_t by multiplying by 32767.0f, which overflows into -32768.
|
||||
// Maximum up will hence never become -32768 with the current version of SDL2,
|
||||
// so this workaround should be safe in compliant browsers.
|
||||
if (lefty == -32768) {
|
||||
lefty = 32767;
|
||||
}
|
||||
if (righty == -32768) {
|
||||
righty = 32767;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rightx < -0x4000) pad->button |= L_CBUTTONS;
|
||||
if (rightx > 0x4000) pad->button |= R_CBUTTONS;
|
||||
if (righty < -0x4000) pad->button |= U_CBUTTONS;
|
||||
if (righty > 0x4000) pad->button |= D_CBUTTONS;
|
||||
|
||||
if (ltrig > 30 * 256) pad->button |= Z_TRIG;
|
||||
if (rtrig > 30 * 256) pad->button |= R_TRIG;
|
||||
|
||||
uint32_t magnitude_sq = (uint32_t)(leftx * leftx) + (uint32_t)(lefty * lefty);
|
||||
if (magnitude_sq > (uint32_t)(DEADZONE * DEADZONE)) {
|
||||
// Game expects stick coordinates within -80..80
|
||||
// 32768 / 409 = ~80
|
||||
pad->stick_x = leftx / 409;
|
||||
pad->stick_y = -lefty / 409;
|
||||
}
|
||||
}
|
||||
|
||||
struct ControllerAPI controller_sdl = {
|
||||
controller_sdl_init,
|
||||
controller_sdl_read
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef CONTROLLER_SDL_H
|
||||
#define CONTROLLER_SDL_H
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
extern struct ControllerAPI controller_sdl;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifdef __linux__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <ultra64.h>
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
void *wup_start(void *a);
|
||||
bool wup_get_controller_input(uint16_t *buttons, uint8_t axis[6]);
|
||||
|
||||
static int8_t saturate(int v) {
|
||||
v = v * 3 / 2;
|
||||
return v < -128 ? -128 : v > 127 ? 127 : v;
|
||||
}
|
||||
|
||||
static void controller_wup_init(void) {
|
||||
pthread_t pid;
|
||||
pthread_create(&pid, NULL, wup_start, NULL);
|
||||
}
|
||||
|
||||
static void controller_wup_read(OSContPad *pad) {
|
||||
uint16_t buttons;
|
||||
uint8_t axis[6];
|
||||
if (wup_get_controller_input(&buttons, axis)) {
|
||||
if (buttons & 0x0001) pad->button |= START_BUTTON;
|
||||
if (buttons & 0x0008) pad->button |= Z_TRIG;
|
||||
if (buttons & 0x0004) pad->button |= R_TRIG;
|
||||
if (buttons & 0x0100) pad->button |= A_BUTTON;
|
||||
if (buttons & 0x0200) pad->button |= B_BUTTON;
|
||||
if (buttons & 0x1000) pad->button |= L_TRIG;
|
||||
if (axis[2] < 0x40) pad->button |= L_CBUTTONS;
|
||||
if (axis[2] > 0xC0) pad->button |= R_CBUTTONS;
|
||||
if (axis[3] < 0x40) pad->button |= D_CBUTTONS;
|
||||
if (axis[3] > 0xC0) pad->button |= U_CBUTTONS;
|
||||
int8_t stick_x = saturate(axis[0] - 128 - 0);
|
||||
int8_t stick_y = saturate(axis[1] - 128 - 0);
|
||||
if (stick_x != 0 || stick_y != 0) {
|
||||
pad->stick_x = stick_x;
|
||||
pad->stick_y = stick_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ControllerAPI controller_wup = {
|
||||
controller_wup_init,
|
||||
controller_wup_read
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CONTROLLER_WUP_H
|
||||
#define CONTROLLER_WUP_H
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
extern struct ControllerAPI controller_wup;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
||||
#include <windows.h>
|
||||
#include <xinput.h>
|
||||
|
||||
#include <ultra64.h>
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
#define DEADZONE 4960
|
||||
|
||||
static void xinput_init(void) {
|
||||
}
|
||||
|
||||
static void xinput_read(OSContPad *pad) {
|
||||
for (int i = 0; i < XUSER_MAX_COUNT; i++) {
|
||||
XINPUT_STATE state;
|
||||
memset(&state, 0, sizeof(XINPUT_STATE));
|
||||
if (XInputGetState(i, &state) == ERROR_SUCCESS) {
|
||||
XINPUT_GAMEPAD *gp = &state.Gamepad;
|
||||
if (gp->wButtons & XINPUT_GAMEPAD_START) pad->button |= START_BUTTON;
|
||||
if (gp->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) pad->button |= Z_TRIG;
|
||||
if (gp->bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) pad->button |= Z_TRIG;
|
||||
if (gp->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) pad->button |= R_TRIG;
|
||||
if (gp->bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) pad->button |= R_TRIG;
|
||||
if (gp->wButtons & XINPUT_GAMEPAD_A) pad->button |= A_BUTTON;
|
||||
if (gp->wButtons & XINPUT_GAMEPAD_X) pad->button |= B_BUTTON;
|
||||
if (gp->wButtons & XINPUT_GAMEPAD_DPAD_LEFT) pad->button |= L_TRIG;
|
||||
if (gp->sThumbRX < -0x4000) pad->button |= L_CBUTTONS;
|
||||
if (gp->sThumbRX > 0x4000) pad->button |= R_CBUTTONS;
|
||||
if (gp->sThumbRY < -0x4000) pad->button |= D_CBUTTONS;
|
||||
if (gp->sThumbRY > 0x4000) pad->button |= U_CBUTTONS;
|
||||
|
||||
uint32_t magnitude_sq = (uint32_t)(gp->sThumbLX * gp->sThumbLX) + (uint32_t)(gp->sThumbLY * gp->sThumbLY);
|
||||
if (magnitude_sq > (uint32_t)(DEADZONE * DEADZONE)) {
|
||||
// Game expects stick coordinates within -80..80
|
||||
// 32768 / 409 = ~80
|
||||
pad->stick_x = gp->sThumbLX / 409;
|
||||
pad->stick_y = gp->sThumbLY / 409;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ControllerAPI controller_xinput = {
|
||||
xinput_init,
|
||||
xinput_read
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CONTROLLER_XINPUT_H
|
||||
#define CONTROLLER_XINPUT_H
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
||||
#include "controller_api.h"
|
||||
|
||||
extern struct ControllerAPI controller_xinput;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,362 @@
|
||||
#if !defined(__MINGW32__) && !defined(__BSD__) && !defined(TARGET_WEB)
|
||||
// See LICENSE for license
|
||||
|
||||
#define _XOPEN_SOURCE 600
|
||||
|
||||
#include <time.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
|
||||
//#include <libudev.h>
|
||||
#include <libusb.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
#if (!defined(LIBUSBX_API_VERSION) || LIBUSBX_API_VERSION < 0x01000102) && (!defined(LIBUSB_API_VERSION) || LIBUSB_API_VERSION < 0x01000102)
|
||||
#error libusb(x) 1.0.16 or higher is required
|
||||
#endif
|
||||
|
||||
#define EP_IN 0x81
|
||||
#define EP_OUT 0x02
|
||||
|
||||
#define STATE_NORMAL 0x10
|
||||
#define STATE_WAVEBIRD 0x20
|
||||
|
||||
const int BUTTON_OFFSET_VALUES[16] = {
|
||||
BTN_START,
|
||||
BTN_TR2,
|
||||
BTN_TR,
|
||||
BTN_TL,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
BTN_SOUTH,
|
||||
BTN_WEST,
|
||||
BTN_EAST,
|
||||
BTN_NORTH,
|
||||
BTN_DPAD_LEFT,
|
||||
BTN_DPAD_RIGHT,
|
||||
BTN_DPAD_DOWN,
|
||||
BTN_DPAD_UP,
|
||||
};
|
||||
|
||||
const int AXIS_OFFSET_VALUES[6] = {
|
||||
ABS_X,
|
||||
ABS_Y,
|
||||
ABS_RX,
|
||||
ABS_RY,
|
||||
ABS_Z,
|
||||
ABS_RZ
|
||||
};
|
||||
|
||||
struct ports
|
||||
{
|
||||
bool connected;
|
||||
bool extra_power;
|
||||
unsigned char type;
|
||||
uint16_t buttons;
|
||||
uint8_t axis[6];
|
||||
};
|
||||
|
||||
struct adapter
|
||||
{
|
||||
volatile bool quitting;
|
||||
struct libusb_device *device;
|
||||
struct libusb_device_handle *handle;
|
||||
pthread_t thread;
|
||||
unsigned char rumble[5];
|
||||
struct ports controllers[4];
|
||||
struct adapter *next;
|
||||
};
|
||||
|
||||
static bool raw_mode;
|
||||
|
||||
static volatile int quitting;
|
||||
|
||||
static struct adapter adapters;
|
||||
|
||||
static const char *uinput_path;
|
||||
|
||||
bool wup_get_controller_input(uint16_t *buttons, uint8_t axis[6]) {
|
||||
struct adapter *adapter = adapters.next;
|
||||
if (adapter != NULL) {
|
||||
*buttons = adapter->controllers[0].buttons;
|
||||
memcpy(axis, adapter->controllers[0].axis, 6);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned char connected_type(unsigned char status)
|
||||
{
|
||||
unsigned char type = status & (STATE_NORMAL | STATE_WAVEBIRD);
|
||||
switch (type)
|
||||
{
|
||||
case STATE_NORMAL:
|
||||
case STATE_WAVEBIRD:
|
||||
return type;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_payload(int i, struct ports *port, unsigned char *payload)
|
||||
{
|
||||
unsigned char status = payload[0];
|
||||
unsigned char type = connected_type(status);
|
||||
|
||||
if (type != 0 && !port->connected)
|
||||
{
|
||||
//uinput_create(i, port, type);
|
||||
port->type = type;
|
||||
port->connected = true;
|
||||
}
|
||||
else if (type == 0 && port->connected)
|
||||
{
|
||||
//uinput_destroy(i, port);
|
||||
port->connected = false;
|
||||
}
|
||||
|
||||
if (!port->connected)
|
||||
return;
|
||||
|
||||
port->extra_power = ((status & 0x04) != 0);
|
||||
|
||||
if (type != port->type)
|
||||
{
|
||||
fprintf(stderr, "controller on port %d changed controller type???", i+1);
|
||||
port->type = type;
|
||||
}
|
||||
|
||||
uint16_t btns = (uint16_t) payload[1] << 8 | (uint16_t) payload[2];
|
||||
port->buttons = btns;
|
||||
//printf("Btns: %04x\n", btns);
|
||||
|
||||
//printf("Axis:");
|
||||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
unsigned char value = payload[j+3];
|
||||
port->axis[j] = value;
|
||||
//printf(" %02x", value);
|
||||
}
|
||||
//puts("");
|
||||
}
|
||||
|
||||
static int64_t to_ms(struct timespec* t) {
|
||||
return t->tv_sec * 1000 + t->tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static void *adapter_thread(void *data)
|
||||
{
|
||||
struct adapter *a = (struct adapter *)data;
|
||||
|
||||
int bytes_transferred;
|
||||
unsigned char payload[1] = { 0x13 };
|
||||
|
||||
int transfer_ret = libusb_interrupt_transfer(a->handle, EP_OUT, payload, sizeof(payload), &bytes_transferred, 0);
|
||||
|
||||
if (transfer_ret != 0) {
|
||||
fprintf(stderr, "libusb_interrupt_transfer: %s\n", libusb_error_name(transfer_ret));
|
||||
return NULL;
|
||||
}
|
||||
if (bytes_transferred != sizeof(payload)) {
|
||||
fprintf(stderr, "libusb_interrupt_transfer %d/%lu bytes transferred.\n", bytes_transferred, sizeof(payload));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (!a->quitting)
|
||||
{
|
||||
//struct timespec time_before = { 0 }, time_after = { 0 };
|
||||
unsigned char payload[37];
|
||||
int size = 0;
|
||||
//clock_gettime(CLOCK_MONOTONIC, &time_before);
|
||||
int transfer_ret = libusb_interrupt_transfer(a->handle, EP_IN, payload, sizeof(payload), &size, 0);
|
||||
//clock_gettime(CLOCK_MONOTONIC, &time_after);
|
||||
//printf("Time taken: %d\n", (int)(to_ms(&time_after) - to_ms(&time_before)));
|
||||
if (transfer_ret != 0) {
|
||||
fprintf(stderr, "libusb_interrupt_transfer error %d\n", transfer_ret);
|
||||
a->quitting = true;
|
||||
break;
|
||||
}
|
||||
if (size != 37 || payload[0] != 0x21)
|
||||
continue;
|
||||
|
||||
unsigned char *controller = &payload[1];
|
||||
|
||||
unsigned char rumble[5] = { 0x11, 0, 0, 0, 0 };
|
||||
//struct timespec current_time = { 0 };
|
||||
//clock_gettime(CLOCK_REALTIME, ¤t_time);
|
||||
//printf("Time: %d %d\n", (int)current_time.tv_sec, (int)current_time.tv_nsec);
|
||||
for (int i = 0; i < 4; i++, controller += 9)
|
||||
{
|
||||
handle_payload(i, &a->controllers[i], controller);
|
||||
rumble[i+1] = 0;
|
||||
/*if (a->controllers[i].extra_power && a->controllers[i].type == STATE_NORMAL)
|
||||
{
|
||||
for (int j = 0; j < MAX_FF_EVENTS; j++)
|
||||
{
|
||||
struct ff_event *e = &a->controllers[i].ff_events[j];
|
||||
if (e->in_use)
|
||||
{
|
||||
if (ts_lessthan(&e->start_time, ¤t_time) && ts_greaterthan(&e->end_time, ¤t_time))
|
||||
rumble[i+1] = 1;
|
||||
else
|
||||
update_ff_start_stop(e, ¤t_time);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
if (memcmp(rumble, a->rumble, sizeof(rumble)) != 0)
|
||||
{
|
||||
memcpy(a->rumble, rumble, sizeof(rumble));
|
||||
transfer_ret = libusb_interrupt_transfer(a->handle, EP_OUT, a->rumble, sizeof(a->rumble), &size, 0);
|
||||
if (transfer_ret != 0) {
|
||||
fprintf(stderr, "libusb_interrupt_transfer error %d\n", transfer_ret);
|
||||
a->quitting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
/*if (a->controllers[i].connected)
|
||||
uinput_destroy(i, &a->controllers[i]);*/
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void add_adapter(struct libusb_device *dev)
|
||||
{
|
||||
struct adapter *a = calloc(1, sizeof(struct adapter));
|
||||
if (a == NULL)
|
||||
{
|
||||
fprintf(stderr, "FATAL: calloc() failed");
|
||||
exit(-1);
|
||||
}
|
||||
a->device = dev;
|
||||
|
||||
if (libusb_open(a->device, &a->handle) != 0)
|
||||
{
|
||||
fprintf(stderr, "Error opening device 0x%p\n", a->device);
|
||||
return;
|
||||
}
|
||||
|
||||
if (libusb_kernel_driver_active(a->handle, 0) == 1) {
|
||||
fprintf(stderr, "Detaching kernel driver\n");
|
||||
if (libusb_detach_kernel_driver(a->handle, 0)) {
|
||||
fprintf(stderr, "Error detaching handle 0x%p from kernel\n", a->handle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct adapter *old_head = adapters.next;
|
||||
adapters.next = a;
|
||||
a->next = old_head;
|
||||
|
||||
pthread_create(&a->thread, NULL, adapter_thread, a);
|
||||
|
||||
fprintf(stderr, "adapter 0x%p connected\n", a->device);
|
||||
}
|
||||
|
||||
static void remove_adapter(struct libusb_device *dev)
|
||||
{
|
||||
struct adapter *a = &adapters;
|
||||
while (a->next != NULL)
|
||||
{
|
||||
if (a->next->device == dev)
|
||||
{
|
||||
a->next->quitting = true;
|
||||
pthread_join(a->next->thread, NULL);
|
||||
fprintf(stderr, "adapter 0x%p disconnected\n", a->next->device);
|
||||
libusb_close(a->next->handle);
|
||||
struct adapter *new_next = a->next->next;
|
||||
free(a->next);
|
||||
a->next = new_next;
|
||||
return;
|
||||
}
|
||||
|
||||
a = a->next;
|
||||
}
|
||||
}
|
||||
|
||||
static int LIBUSB_CALL hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, libusb_hotplug_event event, void *user_data)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)user_data;
|
||||
if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
|
||||
{
|
||||
add_adapter(dev);
|
||||
}
|
||||
else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
|
||||
{
|
||||
remove_adapter(dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *wup_start(UNUSED void *a)
|
||||
{
|
||||
libusb_init(NULL);
|
||||
|
||||
struct libusb_device **devices;
|
||||
|
||||
int count = libusb_get_device_list(NULL, &devices);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
struct libusb_device_descriptor desc;
|
||||
libusb_get_device_descriptor(devices[i], &desc);
|
||||
if (desc.idVendor == 0x057e && desc.idProduct == 0x0337)
|
||||
add_adapter(devices[i]);
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
libusb_free_device_list(devices, 1);
|
||||
|
||||
libusb_hotplug_callback_handle callback;
|
||||
|
||||
int hotplug_capability = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG);
|
||||
if (hotplug_capability) {
|
||||
int hotplug_ret = libusb_hotplug_register_callback(NULL,
|
||||
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
|
||||
LIBUSB_HOTPLUG_NO_FLAGS, 0x057e, 0x0337,
|
||||
LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL, &callback);
|
||||
|
||||
if (hotplug_ret != LIBUSB_SUCCESS) {
|
||||
fprintf(stderr, "cannot register hotplug callback, hotplugging not enabled\n");
|
||||
hotplug_capability = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// pump events until shutdown & all helper threads finish cleaning up
|
||||
while (!quitting)
|
||||
libusb_handle_events_completed(NULL, (int *)&quitting);
|
||||
|
||||
while (adapters.next)
|
||||
remove_adapter(adapters.next->device);
|
||||
|
||||
if (hotplug_capability)
|
||||
libusb_hotplug_deregister_callback(NULL, callback);
|
||||
|
||||
libusb_exit(NULL);
|
||||
return (void *)0;
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2020 Emill, MaikelChan
|
||||
|
||||
Redistribution and use in source forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form are not allowed.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Nintendo 64 Fast3D renderer
|
||||
|
||||
Implementation of a Fast3D renderer for games built originally for the Nintendo 64 platform.
|
||||
|
||||
For rendering OpenGL, Direct3D 11 and Direct3D 12 are supported.
|
||||
|
||||
Supported windowing systems are GLX (used on Linux), DXGI (used on Windows) and SDL (generic).
|
||||
|
||||
# Usage
|
||||
|
||||
See `gfx_pc.h`. You will also need a copy of `PR/gbi.h`, found in libultra.
|
||||
|
||||
First call `gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, const char *game_name, bool start_in_fullscreen)` and supply the desired backends at program start.
|
||||
|
||||
Some callbacks can be set on `wapi`. See `gfx_window_manager_api.h` for more info.
|
||||
|
||||
Each game main loop iteration should look like this:
|
||||
|
||||
```C
|
||||
gfx_start_frame(); // Handles input events such as keyboard and window events
|
||||
// perform game logic here
|
||||
gfx_run(cmds); // submit display list and render a frame
|
||||
// do more expensive work here, such as play audio
|
||||
gfx_end_frame(); // this just waits until the frame is shown on the screen (vsync), to provide correct game timing
|
||||
```
|
||||
|
||||
When you are ready to start the main loop, call `wapi->main_loop(one_iteration_func)`.
|
||||
|
||||
For the best experience, please change the Vtx and Mtx structures to use floats instead of fixed point arithmetic (`GBI_FLOATS`).
|
||||
|
||||
# License
|
||||
|
||||
See LICENSE.txt. Redistributions are allowed only in source form, not in binary form.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
#include "gfx_cc.h"
|
||||
|
||||
void gfx_cc_get_features(uint32_t shader_id, struct CCFeatures *cc_features) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
cc_features->c[0][i] = (shader_id >> (i * 3)) & 7;
|
||||
cc_features->c[1][i] = (shader_id >> (12 + i * 3)) & 7;
|
||||
}
|
||||
|
||||
cc_features->opt_alpha = (shader_id & SHADER_OPT_ALPHA) != 0;
|
||||
cc_features->opt_fog = (shader_id & SHADER_OPT_FOG) != 0;
|
||||
cc_features->opt_texture_edge = (shader_id & SHADER_OPT_TEXTURE_EDGE) != 0;
|
||||
cc_features->opt_noise = (shader_id & SHADER_OPT_NOISE) != 0;
|
||||
|
||||
cc_features->used_textures[0] = false;
|
||||
cc_features->used_textures[1] = false;
|
||||
cc_features->num_inputs = 0;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (cc_features->c[i][j] >= SHADER_INPUT_1 && cc_features->c[i][j] <= SHADER_INPUT_4) {
|
||||
if (cc_features->c[i][j] > cc_features->num_inputs) {
|
||||
cc_features->num_inputs = cc_features->c[i][j];
|
||||
}
|
||||
}
|
||||
if (cc_features->c[i][j] == SHADER_TEXEL0 || cc_features->c[i][j] == SHADER_TEXEL0A) {
|
||||
cc_features->used_textures[0] = true;
|
||||
}
|
||||
if (cc_features->c[i][j] == SHADER_TEXEL1) {
|
||||
cc_features->used_textures[1] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cc_features->do_single[0] = cc_features->c[0][2] == 0;
|
||||
cc_features->do_single[1] = cc_features->c[1][2] == 0;
|
||||
cc_features->do_multiply[0] = cc_features->c[0][1] == 0 && cc_features->c[0][3] == 0;
|
||||
cc_features->do_multiply[1] = cc_features->c[1][1] == 0 && cc_features->c[1][3] == 0;
|
||||
cc_features->do_mix[0] = cc_features->c[0][1] == cc_features->c[0][3];
|
||||
cc_features->do_mix[1] = cc_features->c[1][1] == cc_features->c[1][3];
|
||||
cc_features->color_alpha_same = (shader_id & 0xfff) == ((shader_id >> 12) & 0xfff);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef GFX_CC_H
|
||||
#define GFX_CC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum {
|
||||
CC_0,
|
||||
CC_TEXEL0,
|
||||
CC_TEXEL1,
|
||||
CC_PRIM,
|
||||
CC_SHADE,
|
||||
CC_ENV,
|
||||
CC_TEXEL0A,
|
||||
CC_LOD
|
||||
};
|
||||
|
||||
enum {
|
||||
SHADER_0,
|
||||
SHADER_INPUT_1,
|
||||
SHADER_INPUT_2,
|
||||
SHADER_INPUT_3,
|
||||
SHADER_INPUT_4,
|
||||
SHADER_TEXEL0,
|
||||
SHADER_TEXEL0A,
|
||||
SHADER_TEXEL1
|
||||
};
|
||||
|
||||
#define SHADER_OPT_ALPHA (1 << 24)
|
||||
#define SHADER_OPT_FOG (1 << 25)
|
||||
#define SHADER_OPT_TEXTURE_EDGE (1 << 26)
|
||||
#define SHADER_OPT_NOISE (1 << 27)
|
||||
|
||||
struct CCFeatures {
|
||||
uint8_t c[2][4];
|
||||
bool opt_alpha;
|
||||
bool opt_fog;
|
||||
bool opt_texture_edge;
|
||||
bool opt_noise;
|
||||
bool used_textures[2];
|
||||
int num_inputs;
|
||||
bool do_single[2];
|
||||
bool do_multiply[2];
|
||||
bool do_mix[2];
|
||||
bool color_alpha_same;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void gfx_cc_get_features(uint32_t shader_id, struct CCFeatures *cc_features);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,731 @@
|
||||
#ifdef ENABLE_DX11
|
||||
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include <windows.h>
|
||||
#include <versionhelpers.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include <dxgi1_3.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
|
||||
#ifndef _LANGUAGE_C
|
||||
#define _LANGUAGE_C
|
||||
#endif
|
||||
#include <PR/gbi.h>
|
||||
|
||||
#include "gfx_cc.h"
|
||||
#include "gfx_window_manager_api.h"
|
||||
#include "gfx_rendering_api.h"
|
||||
#include "gfx_direct3d_common.h"
|
||||
|
||||
#define DECLARE_GFX_DXGI_FUNCTIONS
|
||||
#include "gfx_dxgi.h"
|
||||
|
||||
#include "gfx_screen_config.h"
|
||||
|
||||
#define THREE_POINT_FILTERING 0
|
||||
#define DEBUG_D3D 0
|
||||
|
||||
using namespace Microsoft::WRL; // For ComPtr
|
||||
|
||||
namespace {
|
||||
|
||||
struct PerFrameCB {
|
||||
uint32_t noise_frame;
|
||||
float noise_scale_x;
|
||||
float noise_scale_y;
|
||||
uint32_t padding;
|
||||
};
|
||||
|
||||
struct PerDrawCB {
|
||||
struct Texture {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t linear_filtering;
|
||||
uint32_t padding;
|
||||
} textures[2];
|
||||
};
|
||||
|
||||
struct TextureData {
|
||||
ComPtr<ID3D11ShaderResourceView> resource_view;
|
||||
ComPtr<ID3D11SamplerState> sampler_state;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
bool linear_filtering;
|
||||
};
|
||||
|
||||
struct ShaderProgramD3D11 {
|
||||
ComPtr<ID3D11VertexShader> vertex_shader;
|
||||
ComPtr<ID3D11PixelShader> pixel_shader;
|
||||
ComPtr<ID3D11InputLayout> input_layout;
|
||||
ComPtr<ID3D11BlendState> blend_state;
|
||||
|
||||
uint32_t shader_id;
|
||||
uint8_t num_inputs;
|
||||
uint8_t num_floats;
|
||||
bool used_textures[2];
|
||||
};
|
||||
|
||||
static struct {
|
||||
HMODULE d3d11_module;
|
||||
PFN_D3D11_CREATE_DEVICE D3D11CreateDevice;
|
||||
|
||||
HMODULE d3dcompiler_module;
|
||||
pD3DCompile D3DCompile;
|
||||
|
||||
D3D_FEATURE_LEVEL feature_level;
|
||||
|
||||
ComPtr<ID3D11Device> device;
|
||||
ComPtr<IDXGISwapChain1> swap_chain;
|
||||
ComPtr<ID3D11DeviceContext> context;
|
||||
ComPtr<ID3D11RenderTargetView> backbuffer_view;
|
||||
ComPtr<ID3D11DepthStencilView> depth_stencil_view;
|
||||
ComPtr<ID3D11RasterizerState> rasterizer_state;
|
||||
ComPtr<ID3D11DepthStencilState> depth_stencil_state;
|
||||
ComPtr<ID3D11Buffer> vertex_buffer;
|
||||
ComPtr<ID3D11Buffer> per_frame_cb;
|
||||
ComPtr<ID3D11Buffer> per_draw_cb;
|
||||
|
||||
#if DEBUG_D3D
|
||||
ComPtr<ID3D11Debug> debug;
|
||||
#endif
|
||||
|
||||
DXGI_SAMPLE_DESC sample_description;
|
||||
|
||||
PerFrameCB per_frame_cb_data;
|
||||
PerDrawCB per_draw_cb_data;
|
||||
|
||||
struct ShaderProgramD3D11 shader_program_pool[64];
|
||||
uint8_t shader_program_pool_size;
|
||||
|
||||
std::vector<struct TextureData> textures;
|
||||
int current_tile;
|
||||
uint32_t current_texture_ids[2];
|
||||
|
||||
// Current state
|
||||
|
||||
struct ShaderProgramD3D11 *shader_program;
|
||||
|
||||
uint32_t current_width, current_height;
|
||||
|
||||
int8_t depth_test;
|
||||
int8_t depth_mask;
|
||||
int8_t zmode_decal;
|
||||
|
||||
// Previous states (to prevent setting states needlessly)
|
||||
|
||||
struct ShaderProgramD3D11 *last_shader_program = nullptr;
|
||||
uint32_t last_vertex_buffer_stride = 0;
|
||||
ComPtr<ID3D11BlendState> last_blend_state = nullptr;
|
||||
ComPtr<ID3D11ShaderResourceView> last_resource_views[2] = { nullptr, nullptr };
|
||||
ComPtr<ID3D11SamplerState> last_sampler_states[2] = { nullptr, nullptr };
|
||||
int8_t last_depth_test = -1;
|
||||
int8_t last_depth_mask = -1;
|
||||
int8_t last_zmode_decal = -1;
|
||||
D3D_PRIMITIVE_TOPOLOGY last_primitive_topology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
||||
} d3d;
|
||||
|
||||
static LARGE_INTEGER last_time, accumulated_time, frequency;
|
||||
|
||||
static void create_render_target_views(bool is_resize) {
|
||||
DXGI_SWAP_CHAIN_DESC1 desc1;
|
||||
|
||||
if (is_resize) {
|
||||
// Release previous stuff (if any)
|
||||
|
||||
d3d.backbuffer_view.Reset();
|
||||
d3d.depth_stencil_view.Reset();
|
||||
|
||||
// Resize swap chain buffers
|
||||
|
||||
ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1));
|
||||
ThrowIfFailed(d3d.swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, desc1.Flags),
|
||||
gfx_dxgi_get_h_wnd(), "Failed to resize IDXGISwapChain buffers.");
|
||||
}
|
||||
|
||||
// Get new size
|
||||
|
||||
ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1));
|
||||
|
||||
// Create back buffer
|
||||
|
||||
ComPtr<ID3D11Texture2D> backbuffer_texture;
|
||||
ThrowIfFailed(d3d.swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID *) backbuffer_texture.GetAddressOf()),
|
||||
gfx_dxgi_get_h_wnd(), "Failed to get backbuffer from IDXGISwapChain.");
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateRenderTargetView(backbuffer_texture.Get(), nullptr, d3d.backbuffer_view.GetAddressOf()),
|
||||
gfx_dxgi_get_h_wnd(), "Failed to create render target view.");
|
||||
|
||||
// Create depth buffer
|
||||
|
||||
D3D11_TEXTURE2D_DESC depth_stencil_texture_desc;
|
||||
ZeroMemory(&depth_stencil_texture_desc, sizeof(D3D11_TEXTURE2D_DESC));
|
||||
|
||||
depth_stencil_texture_desc.Width = desc1.Width;
|
||||
depth_stencil_texture_desc.Height = desc1.Height;
|
||||
depth_stencil_texture_desc.MipLevels = 1;
|
||||
depth_stencil_texture_desc.ArraySize = 1;
|
||||
depth_stencil_texture_desc.Format = d3d.feature_level >= D3D_FEATURE_LEVEL_10_0 ?
|
||||
DXGI_FORMAT_D32_FLOAT : DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
depth_stencil_texture_desc.SampleDesc = d3d.sample_description;
|
||||
depth_stencil_texture_desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
depth_stencil_texture_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
|
||||
depth_stencil_texture_desc.CPUAccessFlags = 0;
|
||||
depth_stencil_texture_desc.MiscFlags = 0;
|
||||
|
||||
ComPtr<ID3D11Texture2D> depth_stencil_texture;
|
||||
ThrowIfFailed(d3d.device->CreateTexture2D(&depth_stencil_texture_desc, nullptr, depth_stencil_texture.GetAddressOf()));
|
||||
ThrowIfFailed(d3d.device->CreateDepthStencilView(depth_stencil_texture.Get(), nullptr, d3d.depth_stencil_view.GetAddressOf()));
|
||||
|
||||
// Save resolution
|
||||
|
||||
d3d.current_width = desc1.Width;
|
||||
d3d.current_height = desc1.Height;
|
||||
}
|
||||
|
||||
static void gfx_d3d11_init(void) {
|
||||
// Load d3d11.dll
|
||||
d3d.d3d11_module = LoadLibraryW(L"d3d11.dll");
|
||||
if (d3d.d3d11_module == nullptr) {
|
||||
ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "d3d11.dll could not be loaded");
|
||||
}
|
||||
d3d.D3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)GetProcAddress(d3d.d3d11_module, "D3D11CreateDevice");
|
||||
|
||||
// Load D3DCompiler_47.dll or D3DCompiler_43.dll
|
||||
d3d.d3dcompiler_module = LoadLibraryW(L"D3DCompiler_47.dll");
|
||||
if (d3d.d3dcompiler_module == nullptr) {
|
||||
d3d.d3dcompiler_module = LoadLibraryW(L"D3DCompiler_43.dll");
|
||||
if (d3d.d3dcompiler_module == nullptr) {
|
||||
ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "D3DCompiler_47.dll or D3DCompiler_43.dll could not be loaded");
|
||||
}
|
||||
}
|
||||
d3d.D3DCompile = (pD3DCompile)GetProcAddress(d3d.d3dcompiler_module, "D3DCompile");
|
||||
|
||||
// Create D3D11 device
|
||||
|
||||
gfx_dxgi_create_factory_and_device(DEBUG_D3D, 11, [](IDXGIAdapter1 *adapter, bool test_only) {
|
||||
#if DEBUG_D3D
|
||||
UINT device_creation_flags = D3D11_CREATE_DEVICE_DEBUG;
|
||||
#else
|
||||
UINT device_creation_flags = 0;
|
||||
#endif
|
||||
D3D_FEATURE_LEVEL FeatureLevels[] = {
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
D3D_FEATURE_LEVEL_9_2,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
|
||||
HRESULT res = d3d.D3D11CreateDevice(
|
||||
adapter,
|
||||
D3D_DRIVER_TYPE_UNKNOWN, // since we use a specific adapter
|
||||
nullptr,
|
||||
device_creation_flags,
|
||||
FeatureLevels,
|
||||
ARRAYSIZE(FeatureLevels),
|
||||
D3D11_SDK_VERSION,
|
||||
test_only ? nullptr : d3d.device.GetAddressOf(),
|
||||
&d3d.feature_level,
|
||||
test_only ? nullptr : d3d.context.GetAddressOf());
|
||||
|
||||
if (test_only) {
|
||||
return SUCCEEDED(res);
|
||||
} else {
|
||||
ThrowIfFailed(res, gfx_dxgi_get_h_wnd(), "Failed to create D3D11 device.");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Sample description to be used in back buffer and depth buffer
|
||||
|
||||
d3d.sample_description.Count = 1;
|
||||
d3d.sample_description.Quality = 0;
|
||||
|
||||
// Create the swap chain
|
||||
d3d.swap_chain = gfx_dxgi_create_swap_chain(d3d.device.Get());
|
||||
|
||||
// Create D3D Debug device if in debug mode
|
||||
|
||||
#if DEBUG_D3D
|
||||
ThrowIfFailed(d3d.device->QueryInterface(__uuidof(ID3D11Debug), (void **) d3d.debug.GetAddressOf()),
|
||||
gfx_dxgi_get_h_wnd(), "Failed to get ID3D11Debug device.");
|
||||
#endif
|
||||
|
||||
// Create views
|
||||
|
||||
create_render_target_views(false);
|
||||
|
||||
// Create main vertex buffer
|
||||
|
||||
D3D11_BUFFER_DESC vertex_buffer_desc;
|
||||
ZeroMemory(&vertex_buffer_desc, sizeof(D3D11_BUFFER_DESC));
|
||||
|
||||
vertex_buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
vertex_buffer_desc.ByteWidth = 256 * 26 * 3 * sizeof(float); // Same as buf_vbo size in gfx_pc
|
||||
vertex_buffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
||||
vertex_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
vertex_buffer_desc.MiscFlags = 0;
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateBuffer(&vertex_buffer_desc, nullptr, d3d.vertex_buffer.GetAddressOf()),
|
||||
gfx_dxgi_get_h_wnd(), "Failed to create vertex buffer.");
|
||||
|
||||
// Create per-frame constant buffer
|
||||
|
||||
D3D11_BUFFER_DESC constant_buffer_desc;
|
||||
ZeroMemory(&constant_buffer_desc, sizeof(D3D11_BUFFER_DESC));
|
||||
|
||||
constant_buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
constant_buffer_desc.ByteWidth = sizeof(PerFrameCB);
|
||||
constant_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
constant_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
constant_buffer_desc.MiscFlags = 0;
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateBuffer(&constant_buffer_desc, nullptr, d3d.per_frame_cb.GetAddressOf()),
|
||||
gfx_dxgi_get_h_wnd(), "Failed to create per-frame constant buffer.");
|
||||
|
||||
d3d.context->PSSetConstantBuffers(0, 1, d3d.per_frame_cb.GetAddressOf());
|
||||
|
||||
// Create per-draw constant buffer
|
||||
|
||||
constant_buffer_desc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
constant_buffer_desc.ByteWidth = sizeof(PerDrawCB);
|
||||
constant_buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
constant_buffer_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
constant_buffer_desc.MiscFlags = 0;
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateBuffer(&constant_buffer_desc, nullptr, d3d.per_draw_cb.GetAddressOf()),
|
||||
gfx_dxgi_get_h_wnd(), "Failed to create per-draw constant buffer.");
|
||||
|
||||
d3d.context->PSSetConstantBuffers(1, 1, d3d.per_draw_cb.GetAddressOf());
|
||||
}
|
||||
|
||||
|
||||
static bool gfx_d3d11_z_is_from_0_to_1(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_d3d11_unload_shader(struct ShaderProgram *old_prg) {
|
||||
}
|
||||
|
||||
static void gfx_d3d11_load_shader(struct ShaderProgram *new_prg) {
|
||||
d3d.shader_program = (struct ShaderProgramD3D11 *)new_prg;
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_d3d11_create_and_load_new_shader(uint32_t shader_id) {
|
||||
CCFeatures cc_features;
|
||||
gfx_cc_get_features(shader_id, &cc_features);
|
||||
|
||||
char buf[4096];
|
||||
size_t len, num_floats;
|
||||
|
||||
gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, false, THREE_POINT_FILTERING);
|
||||
|
||||
ComPtr<ID3DBlob> vs, ps;
|
||||
ComPtr<ID3DBlob> error_blob;
|
||||
|
||||
#if DEBUG_D3D
|
||||
UINT compile_flags = D3DCOMPILE_DEBUG;
|
||||
#else
|
||||
UINT compile_flags = D3DCOMPILE_OPTIMIZATION_LEVEL2;
|
||||
#endif
|
||||
|
||||
HRESULT hr = d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "VSMain", "vs_4_0_level_9_1", compile_flags, 0, vs.GetAddressOf(), error_blob.GetAddressOf());
|
||||
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(gfx_dxgi_get_h_wnd(), (char *) error_blob->GetBufferPointer(), "Error", MB_OK | MB_ICONERROR);
|
||||
throw hr;
|
||||
}
|
||||
|
||||
hr = d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "PSMain", "ps_4_0_level_9_1", compile_flags, 0, ps.GetAddressOf(), error_blob.GetAddressOf());
|
||||
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(gfx_dxgi_get_h_wnd(), (char *) error_blob->GetBufferPointer(), "Error", MB_OK | MB_ICONERROR);
|
||||
throw hr;
|
||||
}
|
||||
|
||||
struct ShaderProgramD3D11 *prg = &d3d.shader_program_pool[d3d.shader_program_pool_size++];
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateVertexShader(vs->GetBufferPointer(), vs->GetBufferSize(), nullptr, prg->vertex_shader.GetAddressOf()));
|
||||
ThrowIfFailed(d3d.device->CreatePixelShader(ps->GetBufferPointer(), ps->GetBufferSize(), nullptr, prg->pixel_shader.GetAddressOf()));
|
||||
|
||||
// Input Layout
|
||||
|
||||
D3D11_INPUT_ELEMENT_DESC ied[7];
|
||||
uint8_t ied_index = 0;
|
||||
ied[ied_index++] = { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 };
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
ied[ied_index++] = { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 };
|
||||
}
|
||||
if (cc_features.opt_fog) {
|
||||
ied[ied_index++] = { "FOG", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 };
|
||||
}
|
||||
for (unsigned int i = 0; i < cc_features.num_inputs; i++) {
|
||||
DXGI_FORMAT format = cc_features.opt_alpha ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32G32B32_FLOAT;
|
||||
ied[ied_index++] = { "INPUT", i, format, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 };
|
||||
}
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateInputLayout(ied, ied_index, vs->GetBufferPointer(), vs->GetBufferSize(), prg->input_layout.GetAddressOf()));
|
||||
|
||||
// Blend state
|
||||
|
||||
D3D11_BLEND_DESC blend_desc;
|
||||
ZeroMemory(&blend_desc, sizeof(D3D11_BLEND_DESC));
|
||||
|
||||
if (cc_features.opt_alpha) {
|
||||
blend_desc.RenderTarget[0].BlendEnable = true;
|
||||
blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
||||
blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
||||
blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
||||
blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
||||
blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
|
||||
blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
||||
blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
||||
} else {
|
||||
blend_desc.RenderTarget[0].BlendEnable = false;
|
||||
blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
||||
}
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateBlendState(&blend_desc, prg->blend_state.GetAddressOf()));
|
||||
|
||||
// Save some values
|
||||
|
||||
prg->shader_id = shader_id;
|
||||
prg->num_inputs = cc_features.num_inputs;
|
||||
prg->num_floats = num_floats;
|
||||
prg->used_textures[0] = cc_features.used_textures[0];
|
||||
prg->used_textures[1] = cc_features.used_textures[1];
|
||||
|
||||
return (struct ShaderProgram *)(d3d.shader_program = prg);
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_d3d11_lookup_shader(uint32_t shader_id) {
|
||||
for (size_t i = 0; i < d3d.shader_program_pool_size; i++) {
|
||||
if (d3d.shader_program_pool[i].shader_id == shader_id) {
|
||||
return (struct ShaderProgram *)&d3d.shader_program_pool[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void gfx_d3d11_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) {
|
||||
struct ShaderProgramD3D11 *p = (struct ShaderProgramD3D11 *)prg;
|
||||
|
||||
*num_inputs = p->num_inputs;
|
||||
used_textures[0] = p->used_textures[0];
|
||||
used_textures[1] = p->used_textures[1];
|
||||
}
|
||||
|
||||
static uint32_t gfx_d3d11_new_texture(void) {
|
||||
d3d.textures.resize(d3d.textures.size() + 1);
|
||||
return (uint32_t)(d3d.textures.size() - 1);
|
||||
}
|
||||
|
||||
static void gfx_d3d11_select_texture(int tile, uint32_t texture_id) {
|
||||
d3d.current_tile = tile;
|
||||
d3d.current_texture_ids[tile] = texture_id;
|
||||
}
|
||||
|
||||
static D3D11_TEXTURE_ADDRESS_MODE gfx_cm_to_d3d11(uint32_t val) {
|
||||
if (val & G_TX_CLAMP) {
|
||||
return D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
}
|
||||
return (val & G_TX_MIRROR) ? D3D11_TEXTURE_ADDRESS_MIRROR : D3D11_TEXTURE_ADDRESS_WRAP;
|
||||
}
|
||||
|
||||
static void gfx_d3d11_upload_texture(const uint8_t *rgba32_buf, int width, int height) {
|
||||
// Create texture
|
||||
|
||||
D3D11_TEXTURE2D_DESC texture_desc;
|
||||
ZeroMemory(&texture_desc, sizeof(D3D11_TEXTURE2D_DESC));
|
||||
|
||||
texture_desc.Width = width;
|
||||
texture_desc.Height = height;
|
||||
texture_desc.Usage = D3D11_USAGE_IMMUTABLE;
|
||||
texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
texture_desc.CPUAccessFlags = 0;
|
||||
texture_desc.MiscFlags = 0; // D3D11_RESOURCE_MISC_GENERATE_MIPS ?
|
||||
texture_desc.ArraySize = 1;
|
||||
texture_desc.MipLevels = 1;
|
||||
texture_desc.SampleDesc.Count = 1;
|
||||
texture_desc.SampleDesc.Quality = 0;
|
||||
|
||||
D3D11_SUBRESOURCE_DATA resource_data;
|
||||
resource_data.pSysMem = rgba32_buf;
|
||||
resource_data.SysMemPitch = width * 4;
|
||||
resource_data.SysMemSlicePitch = resource_data.SysMemPitch * height;
|
||||
|
||||
ComPtr<ID3D11Texture2D> texture;
|
||||
ThrowIfFailed(d3d.device->CreateTexture2D(&texture_desc, &resource_data, texture.GetAddressOf()));
|
||||
|
||||
// Create shader resource view from texture
|
||||
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC resource_view_desc;
|
||||
ZeroMemory(&resource_view_desc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
|
||||
|
||||
resource_view_desc.Format = texture_desc.Format;
|
||||
resource_view_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
||||
resource_view_desc.Texture2D.MostDetailedMip = 0;
|
||||
resource_view_desc.Texture2D.MipLevels = -1;
|
||||
|
||||
TextureData *texture_data = &d3d.textures[d3d.current_texture_ids[d3d.current_tile]];
|
||||
texture_data->width = width;
|
||||
texture_data->height = height;
|
||||
|
||||
if (texture_data->resource_view.Get() != nullptr) {
|
||||
// Free the previous texture in this slot
|
||||
texture_data->resource_view.Reset();
|
||||
}
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateShaderResourceView(texture.Get(), &resource_view_desc, texture_data->resource_view.GetAddressOf()));
|
||||
}
|
||||
|
||||
static void gfx_d3d11_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) {
|
||||
D3D11_SAMPLER_DESC sampler_desc;
|
||||
ZeroMemory(&sampler_desc, sizeof(D3D11_SAMPLER_DESC));
|
||||
|
||||
#if THREE_POINT_FILTERING
|
||||
sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
||||
#else
|
||||
sampler_desc.Filter = linear_filter ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT;
|
||||
#endif
|
||||
sampler_desc.AddressU = gfx_cm_to_d3d11(cms);
|
||||
sampler_desc.AddressV = gfx_cm_to_d3d11(cmt);
|
||||
sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
|
||||
sampler_desc.MinLOD = 0;
|
||||
sampler_desc.MaxLOD = D3D11_FLOAT32_MAX;
|
||||
|
||||
TextureData *texture_data = &d3d.textures[d3d.current_texture_ids[tile]];
|
||||
texture_data->linear_filtering = linear_filter;
|
||||
|
||||
// This function is called twice per texture, the first one only to set default values.
|
||||
// Maybe that could be skipped? Anyway, make sure to release the first default sampler
|
||||
// state before setting the actual one.
|
||||
texture_data->sampler_state.Reset();
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateSamplerState(&sampler_desc, texture_data->sampler_state.GetAddressOf()));
|
||||
}
|
||||
|
||||
static void gfx_d3d11_set_depth_test(bool depth_test) {
|
||||
d3d.depth_test = depth_test;
|
||||
}
|
||||
|
||||
static void gfx_d3d11_set_depth_mask(bool depth_mask) {
|
||||
d3d.depth_mask = depth_mask;
|
||||
}
|
||||
|
||||
static void gfx_d3d11_set_zmode_decal(bool zmode_decal) {
|
||||
d3d.zmode_decal = zmode_decal;
|
||||
}
|
||||
|
||||
static void gfx_d3d11_set_viewport(int x, int y, int width, int height) {
|
||||
D3D11_VIEWPORT viewport;
|
||||
viewport.TopLeftX = x;
|
||||
viewport.TopLeftY = d3d.current_height - y - height;
|
||||
viewport.Width = width;
|
||||
viewport.Height = height;
|
||||
viewport.MinDepth = 0.0f;
|
||||
viewport.MaxDepth = 1.0f;
|
||||
|
||||
d3d.context->RSSetViewports(1, &viewport);
|
||||
}
|
||||
|
||||
static void gfx_d3d11_set_scissor(int x, int y, int width, int height) {
|
||||
D3D11_RECT rect;
|
||||
rect.left = x;
|
||||
rect.top = d3d.current_height - y - height;
|
||||
rect.right = x + width;
|
||||
rect.bottom = d3d.current_height - y;
|
||||
|
||||
d3d.context->RSSetScissorRects(1, &rect);
|
||||
}
|
||||
|
||||
static void gfx_d3d11_set_use_alpha(bool use_alpha) {
|
||||
// Already part of the pipeline state from shader info
|
||||
}
|
||||
|
||||
static void gfx_d3d11_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) {
|
||||
|
||||
if (d3d.last_depth_test != d3d.depth_test || d3d.last_depth_mask != d3d.depth_mask) {
|
||||
d3d.last_depth_test = d3d.depth_test;
|
||||
d3d.last_depth_mask = d3d.depth_mask;
|
||||
|
||||
d3d.depth_stencil_state.Reset();
|
||||
|
||||
D3D11_DEPTH_STENCIL_DESC depth_stencil_desc;
|
||||
ZeroMemory(&depth_stencil_desc, sizeof(D3D11_DEPTH_STENCIL_DESC));
|
||||
|
||||
depth_stencil_desc.DepthEnable = d3d.depth_test;
|
||||
depth_stencil_desc.DepthWriteMask = d3d.depth_mask ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
|
||||
depth_stencil_desc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
|
||||
depth_stencil_desc.StencilEnable = false;
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateDepthStencilState(&depth_stencil_desc, d3d.depth_stencil_state.GetAddressOf()));
|
||||
d3d.context->OMSetDepthStencilState(d3d.depth_stencil_state.Get(), 0);
|
||||
}
|
||||
|
||||
if (d3d.last_zmode_decal != d3d.zmode_decal) {
|
||||
d3d.last_zmode_decal = d3d.zmode_decal;
|
||||
|
||||
d3d.rasterizer_state.Reset();
|
||||
|
||||
D3D11_RASTERIZER_DESC rasterizer_desc;
|
||||
ZeroMemory(&rasterizer_desc, sizeof(D3D11_RASTERIZER_DESC));
|
||||
|
||||
rasterizer_desc.FillMode = D3D11_FILL_SOLID;
|
||||
rasterizer_desc.CullMode = D3D11_CULL_NONE;
|
||||
rasterizer_desc.FrontCounterClockwise = true;
|
||||
rasterizer_desc.DepthBias = 0;
|
||||
rasterizer_desc.SlopeScaledDepthBias = d3d.zmode_decal ? -2.0f : 0.0f;
|
||||
rasterizer_desc.DepthBiasClamp = 0.0f;
|
||||
rasterizer_desc.DepthClipEnable = true;
|
||||
rasterizer_desc.ScissorEnable = true;
|
||||
rasterizer_desc.MultisampleEnable = false;
|
||||
rasterizer_desc.AntialiasedLineEnable = false;
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateRasterizerState(&rasterizer_desc, d3d.rasterizer_state.GetAddressOf()));
|
||||
d3d.context->RSSetState(d3d.rasterizer_state.Get());
|
||||
}
|
||||
|
||||
bool textures_changed = false;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (d3d.shader_program->used_textures[i]) {
|
||||
if (d3d.last_resource_views[i].Get() != d3d.textures[d3d.current_texture_ids[i]].resource_view.Get()) {
|
||||
d3d.last_resource_views[i] = d3d.textures[d3d.current_texture_ids[i]].resource_view.Get();
|
||||
d3d.context->PSSetShaderResources(i, 1, d3d.textures[d3d.current_texture_ids[i]].resource_view.GetAddressOf());
|
||||
|
||||
#if THREE_POINT_FILTERING
|
||||
d3d.per_draw_cb_data.textures[i].width = d3d.textures[d3d.current_texture_ids[i]].width;
|
||||
d3d.per_draw_cb_data.textures[i].height = d3d.textures[d3d.current_texture_ids[i]].height;
|
||||
d3d.per_draw_cb_data.textures[i].linear_filtering = d3d.textures[d3d.current_texture_ids[i]].linear_filtering;
|
||||
textures_changed = true;
|
||||
#endif
|
||||
|
||||
if (d3d.last_sampler_states[i].Get() != d3d.textures[d3d.current_texture_ids[i]].sampler_state.Get()) {
|
||||
d3d.last_sampler_states[i] = d3d.textures[d3d.current_texture_ids[i]].sampler_state.Get();
|
||||
d3d.context->PSSetSamplers(i, 1, d3d.textures[d3d.current_texture_ids[i]].sampler_state.GetAddressOf());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set per-draw constant buffer
|
||||
|
||||
if (textures_changed) {
|
||||
D3D11_MAPPED_SUBRESOURCE ms;
|
||||
ZeroMemory(&ms, sizeof(D3D11_MAPPED_SUBRESOURCE));
|
||||
d3d.context->Map(d3d.per_draw_cb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
|
||||
memcpy(ms.pData, &d3d.per_draw_cb_data, sizeof(PerDrawCB));
|
||||
d3d.context->Unmap(d3d.per_draw_cb.Get(), 0);
|
||||
}
|
||||
|
||||
// Set vertex buffer data
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE ms;
|
||||
ZeroMemory(&ms, sizeof(D3D11_MAPPED_SUBRESOURCE));
|
||||
d3d.context->Map(d3d.vertex_buffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
|
||||
memcpy(ms.pData, buf_vbo, buf_vbo_len * sizeof(float));
|
||||
d3d.context->Unmap(d3d.vertex_buffer.Get(), 0);
|
||||
|
||||
uint32_t stride = d3d.shader_program->num_floats * sizeof(float);
|
||||
uint32_t offset = 0;
|
||||
|
||||
if (d3d.last_vertex_buffer_stride != stride) {
|
||||
d3d.last_vertex_buffer_stride = stride;
|
||||
d3d.context->IASetVertexBuffers(0, 1, d3d.vertex_buffer.GetAddressOf(), &stride, &offset);
|
||||
}
|
||||
|
||||
if (d3d.last_shader_program != d3d.shader_program) {
|
||||
d3d.last_shader_program = d3d.shader_program;
|
||||
d3d.context->IASetInputLayout(d3d.shader_program->input_layout.Get());
|
||||
d3d.context->VSSetShader(d3d.shader_program->vertex_shader.Get(), 0, 0);
|
||||
d3d.context->PSSetShader(d3d.shader_program->pixel_shader.Get(), 0, 0);
|
||||
|
||||
if (d3d.last_blend_state.Get() != d3d.shader_program->blend_state.Get()) {
|
||||
d3d.last_blend_state = d3d.shader_program->blend_state.Get();
|
||||
d3d.context->OMSetBlendState(d3d.shader_program->blend_state.Get(), 0, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
if (d3d.last_primitive_topology != D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST) {
|
||||
d3d.last_primitive_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
|
||||
d3d.context->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
}
|
||||
|
||||
d3d.context->Draw(buf_vbo_num_tris * 3, 0);
|
||||
}
|
||||
|
||||
static void gfx_d3d11_on_resize(void) {
|
||||
create_render_target_views(true);
|
||||
}
|
||||
|
||||
static void gfx_d3d11_start_frame(void) {
|
||||
// Set render targets
|
||||
|
||||
d3d.context->OMSetRenderTargets(1, d3d.backbuffer_view.GetAddressOf(), d3d.depth_stencil_view.Get());
|
||||
|
||||
// Clear render targets
|
||||
|
||||
const float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
d3d.context->ClearRenderTargetView(d3d.backbuffer_view.Get(), clearColor);
|
||||
d3d.context->ClearDepthStencilView(d3d.depth_stencil_view.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
|
||||
|
||||
// Set per-frame constant buffer
|
||||
|
||||
d3d.per_frame_cb_data.noise_frame++;
|
||||
if (d3d.per_frame_cb_data.noise_frame > 150) {
|
||||
// No high values, as noise starts to look ugly
|
||||
d3d.per_frame_cb_data.noise_frame = 0;
|
||||
}
|
||||
float aspect_ratio = (float) d3d.current_width / (float) d3d.current_height;
|
||||
d3d.per_frame_cb_data.noise_scale_x = 120 * aspect_ratio; // 120 = N64 height resolution (240) / 2
|
||||
d3d.per_frame_cb_data.noise_scale_y = 120;
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE ms;
|
||||
ZeroMemory(&ms, sizeof(D3D11_MAPPED_SUBRESOURCE));
|
||||
d3d.context->Map(d3d.per_frame_cb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
|
||||
memcpy(ms.pData, &d3d.per_frame_cb_data, sizeof(PerFrameCB));
|
||||
d3d.context->Unmap(d3d.per_frame_cb.Get(), 0);
|
||||
}
|
||||
|
||||
static void gfx_d3d11_end_frame(void) {
|
||||
}
|
||||
|
||||
static void gfx_d3d11_finish_render(void) {
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct GfxRenderingAPI gfx_direct3d11_api = {
|
||||
gfx_d3d11_z_is_from_0_to_1,
|
||||
gfx_d3d11_unload_shader,
|
||||
gfx_d3d11_load_shader,
|
||||
gfx_d3d11_create_and_load_new_shader,
|
||||
gfx_d3d11_lookup_shader,
|
||||
gfx_d3d11_shader_get_info,
|
||||
gfx_d3d11_new_texture,
|
||||
gfx_d3d11_select_texture,
|
||||
gfx_d3d11_upload_texture,
|
||||
gfx_d3d11_set_sampler_parameters,
|
||||
gfx_d3d11_set_depth_test,
|
||||
gfx_d3d11_set_depth_mask,
|
||||
gfx_d3d11_set_zmode_decal,
|
||||
gfx_d3d11_set_viewport,
|
||||
gfx_d3d11_set_scissor,
|
||||
gfx_d3d11_set_use_alpha,
|
||||
gfx_d3d11_draw_triangles,
|
||||
gfx_d3d11_init,
|
||||
gfx_d3d11_on_resize,
|
||||
gfx_d3d11_start_frame,
|
||||
gfx_d3d11_end_frame,
|
||||
gfx_d3d11_finish_render
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifdef ENABLE_DX11
|
||||
|
||||
#ifndef GFX_DIRECT3D11_H
|
||||
#define GFX_DIRECT3D11_H
|
||||
|
||||
#include "gfx_rendering_api.h"
|
||||
|
||||
extern struct GfxRenderingAPI gfx_direct3d11_api;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,966 @@
|
||||
#ifdef ENABLE_DX12
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
// This is needed when compiling with MinGW, used in d3d12.h
|
||||
#define __in_ecount_opt(size)
|
||||
|
||||
#include <dxgi.h>
|
||||
#include <dxgi1_4.h>
|
||||
#include "dxsdk/d3d12.h"
|
||||
#include <d3dcompiler.h>
|
||||
|
||||
#include "gfx_direct3d12_guids.h"
|
||||
|
||||
#include "dxsdk/d3dx12.h"
|
||||
|
||||
#ifndef _LANGUAGE_C
|
||||
#define _LANGUAGE_C
|
||||
#endif
|
||||
#include <PR/gbi.h>
|
||||
|
||||
#define DECLARE_GFX_DXGI_FUNCTIONS
|
||||
#include "gfx_dxgi.h"
|
||||
|
||||
#include "gfx_cc.h"
|
||||
#include "gfx_window_manager_api.h"
|
||||
#include "gfx_rendering_api.h"
|
||||
#include "gfx_direct3d_common.h"
|
||||
|
||||
#include "gfx_screen_config.h"
|
||||
|
||||
#define DEBUG_D3D 0
|
||||
|
||||
using namespace Microsoft::WRL; // For ComPtr
|
||||
|
||||
namespace {
|
||||
|
||||
struct ShaderProgramD3D12 {
|
||||
uint32_t shader_id;
|
||||
uint8_t num_inputs;
|
||||
bool used_textures[2];
|
||||
uint8_t num_floats;
|
||||
uint8_t num_attribs;
|
||||
|
||||
ComPtr<ID3DBlob> vertex_shader;
|
||||
ComPtr<ID3DBlob> pixel_shader;
|
||||
ComPtr<ID3D12RootSignature> root_signature;
|
||||
};
|
||||
|
||||
struct PipelineDesc {
|
||||
uint32_t shader_id;
|
||||
bool depth_test;
|
||||
bool depth_mask;
|
||||
bool zmode_decal;
|
||||
bool _padding;
|
||||
|
||||
bool operator==(const PipelineDesc& o) const {
|
||||
return memcmp(this, &o, sizeof(*this)) == 0;
|
||||
}
|
||||
|
||||
bool operator<(const PipelineDesc& o) const {
|
||||
return memcmp(this, &o, sizeof(*this)) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureHeap {
|
||||
ComPtr<ID3D12Heap> heap;
|
||||
std::vector<uint8_t> free_list;
|
||||
};
|
||||
|
||||
struct TextureData {
|
||||
ComPtr<ID3D12Resource> resource;
|
||||
struct TextureHeap *heap;
|
||||
uint8_t heap_offset;
|
||||
|
||||
uint64_t last_frame_counter;
|
||||
uint32_t descriptor_index;
|
||||
int sampler_parameters;
|
||||
};
|
||||
|
||||
struct NoiseCB {
|
||||
uint32_t noise_frame;
|
||||
float noise_scale_x;
|
||||
float noise_scale_y;
|
||||
uint32_t padding;
|
||||
};
|
||||
|
||||
static struct {
|
||||
HMODULE d3d12_module;
|
||||
PFN_D3D12_CREATE_DEVICE D3D12CreateDevice;
|
||||
PFN_D3D12_GET_DEBUG_INTERFACE D3D12GetDebugInterface;
|
||||
|
||||
HMODULE d3dcompiler_module;
|
||||
pD3DCompile D3DCompile;
|
||||
|
||||
struct ShaderProgramD3D12 shader_program_pool[64];
|
||||
uint8_t shader_program_pool_size;
|
||||
|
||||
uint32_t current_width, current_height;
|
||||
|
||||
ComPtr<ID3D12Device> device;
|
||||
ComPtr<ID3D12CommandQueue> command_queue;
|
||||
ComPtr<ID3D12CommandQueue> copy_command_queue;
|
||||
ComPtr<IDXGISwapChain3> swap_chain;
|
||||
ComPtr<ID3D12DescriptorHeap> rtv_heap;
|
||||
UINT rtv_descriptor_size;
|
||||
ComPtr<ID3D12Resource> render_targets[2];
|
||||
ComPtr<ID3D12CommandAllocator> command_allocator;
|
||||
ComPtr<ID3D12CommandAllocator> copy_command_allocator;
|
||||
ComPtr<ID3D12GraphicsCommandList> command_list;
|
||||
ComPtr<ID3D12GraphicsCommandList> copy_command_list;
|
||||
ComPtr<ID3D12DescriptorHeap> dsv_heap;
|
||||
ComPtr<ID3D12Resource> depth_stencil_buffer;
|
||||
ComPtr<ID3D12DescriptorHeap> srv_heap;
|
||||
UINT srv_descriptor_size;
|
||||
ComPtr<ID3D12DescriptorHeap> sampler_heap;
|
||||
UINT sampler_descriptor_size;
|
||||
|
||||
std::map<std::pair<uint32_t, uint32_t>, std::list<struct TextureHeap>> texture_heaps;
|
||||
|
||||
std::map<size_t, std::vector<ComPtr<ID3D12Resource>>> upload_heaps;
|
||||
std::vector<std::pair<size_t, ComPtr<ID3D12Resource>>> upload_heaps_in_flight;
|
||||
ComPtr<ID3D12Fence> copy_fence;
|
||||
uint64_t copy_fence_value;
|
||||
|
||||
std::vector<struct TextureData> textures;
|
||||
int current_tile;
|
||||
uint32_t current_texture_ids[2];
|
||||
uint32_t srv_pos;
|
||||
|
||||
int frame_index;
|
||||
ComPtr<ID3D12Fence> fence;
|
||||
HANDLE fence_event;
|
||||
|
||||
uint64_t frame_counter;
|
||||
|
||||
ComPtr<ID3D12Resource> noise_cb;
|
||||
void *mapped_noise_cb_address;
|
||||
struct NoiseCB noise_cb_data;
|
||||
|
||||
ComPtr<ID3D12Resource> vertex_buffer;
|
||||
void *mapped_vbuf_address;
|
||||
int vbuf_pos;
|
||||
|
||||
std::vector<ComPtr<ID3D12Resource>> resources_to_clean_at_end_of_frame;
|
||||
std::vector<std::pair<struct TextureHeap *, uint8_t>> texture_heap_allocations_to_reclaim_at_end_of_frame;
|
||||
|
||||
std::map<PipelineDesc, ComPtr<ID3D12PipelineState>> pipeline_states;
|
||||
bool must_reload_pipeline;
|
||||
|
||||
// Current state:
|
||||
ID3D12PipelineState *pipeline_state;
|
||||
struct ShaderProgramD3D12 *shader_program;
|
||||
bool depth_test;
|
||||
bool depth_mask;
|
||||
bool zmode_decal;
|
||||
|
||||
CD3DX12_VIEWPORT viewport;
|
||||
CD3DX12_RECT scissor;
|
||||
} d3d;
|
||||
|
||||
static int texture_uploads = 0;
|
||||
static int max_texture_uploads;
|
||||
|
||||
static D3D12_CPU_DESCRIPTOR_HANDLE get_cpu_descriptor_handle(ComPtr<ID3D12DescriptorHeap>& heap) {
|
||||
#ifdef __MINGW32__
|
||||
// We would like to do this:
|
||||
// D3D12_CPU_DESCRIPTOR_HANDLE handle = heap->GetCPUDescriptorHandleForHeapStart();
|
||||
// but MinGW64 doesn't follow the calling conventions of VC++ for some reason.
|
||||
// Per MS documentation "User-defined types can be returned by value from global functions and static member functions"...
|
||||
// "Otherwise, the caller assumes the responsibility of allocating memory and passing a pointer for the return value as the first argument".
|
||||
// The method here is a non-static member function, and hence we need to pass the address to the return value as a parameter.
|
||||
// MinGW32 has the same issue.
|
||||
auto fn = heap->GetCPUDescriptorHandleForHeapStart;
|
||||
void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*fun)(D3D12_CPU_DESCRIPTOR_HANDLE *out) = (void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*)(D3D12_CPU_DESCRIPTOR_HANDLE *out))fn;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE handle;
|
||||
(heap.Get()->*fun)(&handle);
|
||||
return handle;
|
||||
#else
|
||||
return heap->GetCPUDescriptorHandleForHeapStart();
|
||||
#endif
|
||||
}
|
||||
|
||||
static D3D12_GPU_DESCRIPTOR_HANDLE get_gpu_descriptor_handle(ComPtr<ID3D12DescriptorHeap>& heap) {
|
||||
#ifdef __MINGW32__
|
||||
// See get_cpu_descriptor_handle
|
||||
auto fn = heap->GetGPUDescriptorHandleForHeapStart;
|
||||
void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*fun)(D3D12_GPU_DESCRIPTOR_HANDLE *out) = (void (STDMETHODCALLTYPE ID3D12DescriptorHeap::*)(D3D12_GPU_DESCRIPTOR_HANDLE *out))fn;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
||||
(heap.Get()->*fun)(&handle);
|
||||
return handle;
|
||||
#else
|
||||
return heap->GetGPUDescriptorHandleForHeapStart();
|
||||
#endif
|
||||
}
|
||||
|
||||
static D3D12_RESOURCE_ALLOCATION_INFO get_resource_allocation_info(const D3D12_RESOURCE_DESC *resource_desc) {
|
||||
#ifdef __MINGW32__
|
||||
// See get_cpu_descriptor_handle
|
||||
auto fn = d3d.device->GetResourceAllocationInfo;
|
||||
void (STDMETHODCALLTYPE ID3D12Device::*fun)(D3D12_RESOURCE_ALLOCATION_INFO *out, UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs) =
|
||||
(void (STDMETHODCALLTYPE ID3D12Device::*)(D3D12_RESOURCE_ALLOCATION_INFO *out, UINT visibleMask, UINT numResourceDescs, const D3D12_RESOURCE_DESC *pResourceDescs))fn;
|
||||
D3D12_RESOURCE_ALLOCATION_INFO out;
|
||||
(d3d.device.Get()->*fun)(&out, 0, 1, resource_desc);
|
||||
return out;
|
||||
#else
|
||||
return d3d.device->GetResourceAllocationInfo(0, 1, resource_desc);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool gfx_direct3d12_z_is_from_0_to_1(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_unload_shader(struct ShaderProgram *old_prg) {
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_load_shader(struct ShaderProgram *new_prg) {
|
||||
d3d.shader_program = (struct ShaderProgramD3D12 *)new_prg;
|
||||
d3d.must_reload_pipeline = true;
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_direct3d12_create_and_load_new_shader(uint32_t shader_id) {
|
||||
/*static FILE *fp;
|
||||
if (!fp) {
|
||||
fp = fopen("shaders.txt", "w");
|
||||
}
|
||||
fprintf(fp, "0x%08x\n", shader_id);
|
||||
fflush(fp);*/
|
||||
|
||||
struct ShaderProgramD3D12 *prg = &d3d.shader_program_pool[d3d.shader_program_pool_size++];
|
||||
|
||||
CCFeatures cc_features;
|
||||
gfx_cc_get_features(shader_id, &cc_features);
|
||||
|
||||
char buf[2048];
|
||||
size_t len, num_floats;
|
||||
|
||||
gfx_direct3d_common_build_shader(buf, len, num_floats, cc_features, true, false);
|
||||
|
||||
//fwrite(buf, 1, len, stdout);
|
||||
|
||||
ThrowIfFailed(d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "VSMain", "vs_5_1", D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &prg->vertex_shader, nullptr));
|
||||
ThrowIfFailed(d3d.D3DCompile(buf, len, nullptr, nullptr, nullptr, "PSMain", "ps_5_1", D3DCOMPILE_OPTIMIZATION_LEVEL3, 0, &prg->pixel_shader, nullptr));
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateRootSignature(0, prg->pixel_shader->GetBufferPointer(), prg->pixel_shader->GetBufferSize(), IID_PPV_ARGS(&prg->root_signature)));
|
||||
|
||||
prg->shader_id = shader_id;
|
||||
prg->num_inputs = cc_features.num_inputs;
|
||||
prg->used_textures[0] = cc_features.used_textures[0];
|
||||
prg->used_textures[1] = cc_features.used_textures[1];
|
||||
prg->num_floats = num_floats;
|
||||
//prg->num_attribs = cnt;
|
||||
|
||||
d3d.must_reload_pipeline = true;
|
||||
return (struct ShaderProgram *)(d3d.shader_program = prg);
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_direct3d12_lookup_shader(uint32_t shader_id) {
|
||||
for (size_t i = 0; i < d3d.shader_program_pool_size; i++) {
|
||||
if (d3d.shader_program_pool[i].shader_id == shader_id) {
|
||||
return (struct ShaderProgram *)&d3d.shader_program_pool[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) {
|
||||
struct ShaderProgramD3D12 *p = (struct ShaderProgramD3D12 *)prg;
|
||||
|
||||
*num_inputs = p->num_inputs;
|
||||
used_textures[0] = p->used_textures[0];
|
||||
used_textures[1] = p->used_textures[1];
|
||||
}
|
||||
|
||||
static uint32_t gfx_direct3d12_new_texture(void) {
|
||||
d3d.textures.resize(d3d.textures.size() + 1);
|
||||
return (uint32_t)(d3d.textures.size() - 1);
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_select_texture(int tile, uint32_t texture_id) {
|
||||
d3d.current_tile = tile;
|
||||
d3d.current_texture_ids[tile] = texture_id;
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_upload_texture(const uint8_t *rgba32_buf, int width, int height) {
|
||||
texture_uploads++;
|
||||
|
||||
ComPtr<ID3D12Resource> texture_resource;
|
||||
|
||||
// Describe and create a Texture2D.
|
||||
D3D12_RESOURCE_DESC texture_desc = {};
|
||||
texture_desc.MipLevels = 1;
|
||||
texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
texture_desc.Width = width;
|
||||
texture_desc.Height = height;
|
||||
texture_desc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
||||
texture_desc.DepthOrArraySize = 1;
|
||||
texture_desc.SampleDesc.Count = 1;
|
||||
texture_desc.SampleDesc.Quality = 0;
|
||||
texture_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
texture_desc.Alignment = ((width + 31) / 32) * ((height + 31) / 32) > 16 ? 0 : D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT;
|
||||
|
||||
D3D12_RESOURCE_ALLOCATION_INFO alloc_info = get_resource_allocation_info(&texture_desc);
|
||||
|
||||
std::list<struct TextureHeap>& heaps = d3d.texture_heaps[std::pair<uint32_t, uint32_t>(alloc_info.SizeInBytes, alloc_info.Alignment)];
|
||||
|
||||
struct TextureHeap *found_heap = nullptr;
|
||||
for (struct TextureHeap& heap : heaps) {
|
||||
if (!heap.free_list.empty()) {
|
||||
found_heap = &heap;
|
||||
}
|
||||
}
|
||||
if (found_heap == nullptr) {
|
||||
heaps.resize(heaps.size() + 1);
|
||||
found_heap = &heaps.back();
|
||||
|
||||
// In case of HD textures, make sure too much memory isn't wasted
|
||||
int textures_per_heap = 524288 / alloc_info.SizeInBytes;
|
||||
if (textures_per_heap < 1) {
|
||||
textures_per_heap = 1;
|
||||
} else if (textures_per_heap > 64) {
|
||||
textures_per_heap = 64;
|
||||
}
|
||||
|
||||
D3D12_HEAP_DESC heap_desc = {};
|
||||
heap_desc.SizeInBytes = alloc_info.SizeInBytes * textures_per_heap;
|
||||
if (alloc_info.Alignment == D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT) {
|
||||
heap_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
|
||||
} else {
|
||||
heap_desc.Alignment = alloc_info.Alignment;
|
||||
}
|
||||
heap_desc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
|
||||
heap_desc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
|
||||
heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
heap_desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES;
|
||||
ThrowIfFailed(d3d.device->CreateHeap(&heap_desc, IID_PPV_ARGS(&found_heap->heap)));
|
||||
for (int i = 0; i < textures_per_heap; i++) {
|
||||
found_heap->free_list.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t heap_offset = found_heap->free_list.back();
|
||||
found_heap->free_list.pop_back();
|
||||
ThrowIfFailed(d3d.device->CreatePlacedResource(found_heap->heap.Get(), heap_offset * alloc_info.SizeInBytes, &texture_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&texture_resource)));
|
||||
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
|
||||
UINT num_rows;
|
||||
UINT64 row_size_in_bytes;
|
||||
UINT64 upload_buffer_size;
|
||||
d3d.device->GetCopyableFootprints(&texture_desc, 0, 1, 0, &layout, &num_rows, &row_size_in_bytes, &upload_buffer_size);
|
||||
|
||||
std::vector<ComPtr<ID3D12Resource>>& upload_heaps = d3d.upload_heaps[upload_buffer_size];
|
||||
ComPtr<ID3D12Resource> upload_heap;
|
||||
if (upload_heaps.empty()) {
|
||||
CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD);
|
||||
CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(upload_buffer_size);
|
||||
ThrowIfFailed(d3d.device->CreateCommittedResource(
|
||||
&hp,
|
||||
D3D12_HEAP_FLAG_NONE,
|
||||
&rdb,
|
||||
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||
nullptr,
|
||||
IID_PPV_ARGS(&upload_heap)));
|
||||
} else {
|
||||
upload_heap = upload_heaps.back();
|
||||
upload_heaps.pop_back();
|
||||
}
|
||||
|
||||
{
|
||||
D3D12_SUBRESOURCE_DATA texture_data = {};
|
||||
texture_data.pData = rgba32_buf;
|
||||
texture_data.RowPitch = width * 4; // RGBA
|
||||
texture_data.SlicePitch = texture_data.RowPitch * height;
|
||||
|
||||
void *data;
|
||||
upload_heap->Map(0, nullptr, &data);
|
||||
D3D12_MEMCPY_DEST dest_data = { (uint8_t *)data + layout.Offset, layout.Footprint.RowPitch, SIZE_T(layout.Footprint.RowPitch) * SIZE_T(num_rows) };
|
||||
MemcpySubresource(&dest_data, &texture_data, static_cast<SIZE_T>(row_size_in_bytes), num_rows, layout.Footprint.Depth);
|
||||
upload_heap->Unmap(0, nullptr);
|
||||
|
||||
CD3DX12_TEXTURE_COPY_LOCATION dst(texture_resource.Get(), 0);
|
||||
CD3DX12_TEXTURE_COPY_LOCATION src(upload_heap.Get(), layout);
|
||||
d3d.copy_command_list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr);
|
||||
}
|
||||
|
||||
CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(texture_resource.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
||||
d3d.command_list->ResourceBarrier(1, &barrier);
|
||||
|
||||
d3d.upload_heaps_in_flight.push_back(std::make_pair((size_t)upload_buffer_size, std::move(upload_heap)));
|
||||
|
||||
struct TextureData& td = d3d.textures[d3d.current_texture_ids[d3d.current_tile]];
|
||||
if (td.resource.Get() != nullptr) {
|
||||
d3d.resources_to_clean_at_end_of_frame.push_back(std::move(td.resource));
|
||||
d3d.texture_heap_allocations_to_reclaim_at_end_of_frame.push_back(std::make_pair(td.heap, td.heap_offset));
|
||||
td.last_frame_counter = 0;
|
||||
}
|
||||
td.resource = std::move(texture_resource);
|
||||
td.heap = found_heap;
|
||||
td.heap_offset = heap_offset;
|
||||
}
|
||||
|
||||
static int gfx_cm_to_index(uint32_t val) {
|
||||
if (val & G_TX_CLAMP) {
|
||||
return 2;
|
||||
}
|
||||
return (val & G_TX_MIRROR) ? 1 : 0;
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) {
|
||||
d3d.textures[d3d.current_texture_ids[tile]].sampler_parameters = linear_filter * 9 + gfx_cm_to_index(cms) * 3 + gfx_cm_to_index(cmt);
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_set_depth_test(bool depth_test) {
|
||||
d3d.depth_test = depth_test;
|
||||
d3d.must_reload_pipeline = true;
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_set_depth_mask(bool z_upd) {
|
||||
d3d.depth_mask = z_upd;
|
||||
d3d.must_reload_pipeline = true;
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_set_zmode_decal(bool zmode_decal) {
|
||||
d3d.zmode_decal = zmode_decal;
|
||||
d3d.must_reload_pipeline = true;
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_set_viewport(int x, int y, int width, int height) {
|
||||
d3d.viewport = CD3DX12_VIEWPORT(x, d3d.current_height - y - height, width, height);
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_set_scissor(int x, int y, int width, int height) {
|
||||
d3d.scissor = CD3DX12_RECT(x, d3d.current_height - y - height, x + width, d3d.current_height - y);
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_set_use_alpha(bool use_alpha) {
|
||||
// Already part of the pipeline state from shader info
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) {
|
||||
struct ShaderProgramD3D12 *prg = d3d.shader_program;
|
||||
|
||||
if (d3d.must_reload_pipeline) {
|
||||
ComPtr<ID3D12PipelineState>& pipeline_state = d3d.pipeline_states[PipelineDesc{
|
||||
prg->shader_id,
|
||||
d3d.depth_test,
|
||||
d3d.depth_mask,
|
||||
d3d.zmode_decal,
|
||||
0
|
||||
}];
|
||||
if (pipeline_state.Get() == nullptr) {
|
||||
D3D12_INPUT_ELEMENT_DESC ied[7] = {
|
||||
{"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
|
||||
};
|
||||
uint32_t ied_pos = 1;
|
||||
if (prg->used_textures[0] || prg->used_textures[1]) {
|
||||
ied[ied_pos++] = D3D12_INPUT_ELEMENT_DESC{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0};
|
||||
}
|
||||
if (prg->shader_id & SHADER_OPT_FOG) {
|
||||
ied[ied_pos++] = D3D12_INPUT_ELEMENT_DESC{"FOG", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0};
|
||||
}
|
||||
for (int i = 0; i < prg->num_inputs; i++) {
|
||||
DXGI_FORMAT format = (prg->shader_id & SHADER_OPT_ALPHA) ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32G32B32_FLOAT;
|
||||
ied[ied_pos++] = D3D12_INPUT_ELEMENT_DESC{"INPUT", (UINT)i, format, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0};
|
||||
}
|
||||
|
||||
D3D12_GRAPHICS_PIPELINE_STATE_DESC desc = {};
|
||||
desc.InputLayout = { ied, ied_pos };
|
||||
desc.pRootSignature = prg->root_signature.Get();
|
||||
desc.VS = CD3DX12_SHADER_BYTECODE(prg->vertex_shader.Get());
|
||||
desc.PS = CD3DX12_SHADER_BYTECODE(prg->pixel_shader.Get());
|
||||
desc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
|
||||
if (d3d.zmode_decal) {
|
||||
desc.RasterizerState.SlopeScaledDepthBias = -2.0f;
|
||||
}
|
||||
desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
|
||||
if (prg->shader_id & SHADER_OPT_ALPHA) {
|
||||
D3D12_BLEND_DESC bd = {};
|
||||
bd.AlphaToCoverageEnable = FALSE;
|
||||
bd.IndependentBlendEnable = FALSE;
|
||||
static const D3D12_RENDER_TARGET_BLEND_DESC default_rtbd = {
|
||||
TRUE, FALSE,
|
||||
D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD,
|
||||
D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD,
|
||||
D3D12_LOGIC_OP_NOOP,
|
||||
D3D12_COLOR_WRITE_ENABLE_ALL
|
||||
};
|
||||
for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) {
|
||||
bd.RenderTarget[i] = default_rtbd;
|
||||
}
|
||||
desc.BlendState = bd;
|
||||
} else {
|
||||
desc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
|
||||
}
|
||||
desc.DepthStencilState.DepthEnable = d3d.depth_test;
|
||||
desc.DepthStencilState.DepthWriteMask = d3d.depth_mask ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
|
||||
desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
|
||||
desc.DSVFormat = d3d.depth_test ? DXGI_FORMAT_D32_FLOAT : DXGI_FORMAT_UNKNOWN;
|
||||
desc.SampleMask = UINT_MAX;
|
||||
desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
||||
desc.NumRenderTargets = 1;
|
||||
desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
ThrowIfFailed(d3d.device->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&pipeline_state)));
|
||||
}
|
||||
d3d.pipeline_state = pipeline_state.Get();
|
||||
d3d.must_reload_pipeline = false;
|
||||
}
|
||||
|
||||
d3d.command_list->SetGraphicsRootSignature(prg->root_signature.Get());
|
||||
d3d.command_list->SetPipelineState(d3d.pipeline_state);
|
||||
|
||||
ID3D12DescriptorHeap *heaps[] = { d3d.srv_heap.Get(), d3d.sampler_heap.Get() };
|
||||
d3d.command_list->SetDescriptorHeaps(2, heaps);
|
||||
|
||||
int root_param_index = 0;
|
||||
|
||||
if ((prg->shader_id & (SHADER_OPT_ALPHA | SHADER_OPT_NOISE)) == (SHADER_OPT_ALPHA | SHADER_OPT_NOISE)) {
|
||||
d3d.command_list->SetGraphicsRootConstantBufferView(root_param_index++, d3d.noise_cb->GetGPUVirtualAddress());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (prg->used_textures[i]) {
|
||||
struct TextureData& td = d3d.textures[d3d.current_texture_ids[i]];
|
||||
if (td.last_frame_counter != d3d.frame_counter) {
|
||||
td.descriptor_index = d3d.srv_pos;
|
||||
td.last_frame_counter = d3d.frame_counter;
|
||||
|
||||
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
|
||||
srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
||||
srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||
srv_desc.Texture2D.MipLevels = 1;
|
||||
|
||||
CD3DX12_CPU_DESCRIPTOR_HANDLE srv_handle(get_cpu_descriptor_handle(d3d.srv_heap), d3d.srv_pos++, d3d.srv_descriptor_size);
|
||||
d3d.device->CreateShaderResourceView(td.resource.Get(), &srv_desc, srv_handle);
|
||||
}
|
||||
|
||||
CD3DX12_GPU_DESCRIPTOR_HANDLE srv_gpu_handle(get_gpu_descriptor_handle(d3d.srv_heap), td.descriptor_index, d3d.srv_descriptor_size);
|
||||
d3d.command_list->SetGraphicsRootDescriptorTable(root_param_index++, srv_gpu_handle);
|
||||
|
||||
CD3DX12_GPU_DESCRIPTOR_HANDLE sampler_gpu_handle(get_gpu_descriptor_handle(d3d.sampler_heap), td.sampler_parameters, d3d.sampler_descriptor_size);
|
||||
d3d.command_list->SetGraphicsRootDescriptorTable(root_param_index++, sampler_gpu_handle);
|
||||
}
|
||||
}
|
||||
|
||||
CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(get_cpu_descriptor_handle(d3d.rtv_heap), d3d.frame_index, d3d.rtv_descriptor_size);
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle = get_cpu_descriptor_handle(d3d.dsv_heap);
|
||||
d3d.command_list->OMSetRenderTargets(1, &rtv_handle, FALSE, &dsv_handle);
|
||||
|
||||
d3d.command_list->RSSetViewports(1, &d3d.viewport);
|
||||
d3d.command_list->RSSetScissorRects(1, &d3d.scissor);
|
||||
|
||||
int current_pos = d3d.vbuf_pos;
|
||||
memcpy((uint8_t *)d3d.mapped_vbuf_address + current_pos, buf_vbo, buf_vbo_len * sizeof(float));
|
||||
d3d.vbuf_pos += buf_vbo_len * sizeof(float);
|
||||
static int maxpos;
|
||||
if (d3d.vbuf_pos > maxpos) {
|
||||
maxpos = d3d.vbuf_pos;
|
||||
//printf("NEW MAXPOS: %d\n", maxpos);
|
||||
}
|
||||
|
||||
D3D12_VERTEX_BUFFER_VIEW vertex_buffer_view;
|
||||
vertex_buffer_view.BufferLocation = d3d.vertex_buffer->GetGPUVirtualAddress() + current_pos;
|
||||
vertex_buffer_view.StrideInBytes = buf_vbo_len / (3 * buf_vbo_num_tris) * sizeof(float);
|
||||
vertex_buffer_view.SizeInBytes = buf_vbo_len * sizeof(float);
|
||||
|
||||
d3d.command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
d3d.command_list->IASetVertexBuffers(0, 1, &vertex_buffer_view);
|
||||
d3d.command_list->DrawInstanced(3 * buf_vbo_num_tris, 1, 0, 0);
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_start_frame(void) {
|
||||
++d3d.frame_counter;
|
||||
d3d.srv_pos = 0;
|
||||
texture_uploads = 0;
|
||||
ThrowIfFailed(d3d.command_allocator->Reset());
|
||||
ThrowIfFailed(d3d.command_list->Reset(d3d.command_allocator.Get(), nullptr));
|
||||
|
||||
CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
|
||||
d3d.render_targets[d3d.frame_index].Get(),
|
||||
D3D12_RESOURCE_STATE_PRESENT,
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||
d3d.command_list->ResourceBarrier(1, &barrier);
|
||||
|
||||
CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(get_cpu_descriptor_handle(d3d.rtv_heap), d3d.frame_index, d3d.rtv_descriptor_size);
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle = get_cpu_descriptor_handle(d3d.dsv_heap);
|
||||
d3d.command_list->OMSetRenderTargets(1, &rtv_handle, FALSE, &dsv_handle);
|
||||
|
||||
static unsigned char c;
|
||||
const float clear_color[] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
d3d.command_list->ClearRenderTargetView(rtv_handle, clear_color, 0, nullptr);
|
||||
d3d.command_list->ClearDepthStencilView(dsv_handle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
|
||||
|
||||
d3d.noise_cb_data.noise_frame++;
|
||||
if (d3d.noise_cb_data.noise_frame > 150) {
|
||||
// No high values, as noise starts to look ugly
|
||||
d3d.noise_cb_data.noise_frame = 0;
|
||||
}
|
||||
float aspect_ratio = (float) d3d.current_width / (float) d3d.current_height;
|
||||
d3d.noise_cb_data.noise_scale_x = 120 * aspect_ratio; // 120 = N64 height resolution (240) / 2
|
||||
d3d.noise_cb_data.noise_scale_y = 120;
|
||||
memcpy(d3d.mapped_noise_cb_address, &d3d.noise_cb_data, sizeof(struct NoiseCB));
|
||||
|
||||
d3d.vbuf_pos = 0;
|
||||
}
|
||||
|
||||
static void create_render_target_views(void) {
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = get_cpu_descriptor_handle(d3d.rtv_heap);
|
||||
for (UINT i = 0; i < 2; i++) {
|
||||
ThrowIfFailed(d3d.swap_chain->GetBuffer(i, IID_ID3D12Resource, (void **)&d3d.render_targets[i]));
|
||||
d3d.device->CreateRenderTargetView(d3d.render_targets[i].Get(), nullptr, rtv_handle);
|
||||
rtv_handle.ptr += d3d.rtv_descriptor_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void create_depth_buffer(void) {
|
||||
DXGI_SWAP_CHAIN_DESC1 desc1;
|
||||
ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1));
|
||||
UINT width = desc1.Width;
|
||||
UINT height = desc1.Height;
|
||||
|
||||
d3d.current_width = width;
|
||||
d3d.current_height = height;
|
||||
|
||||
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc = {};
|
||||
dsv_desc.Format = DXGI_FORMAT_D32_FLOAT;
|
||||
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
|
||||
dsv_desc.Flags = D3D12_DSV_FLAG_NONE;
|
||||
|
||||
D3D12_CLEAR_VALUE depth_optimized_cv = {};
|
||||
depth_optimized_cv.Format = DXGI_FORMAT_D32_FLOAT;
|
||||
depth_optimized_cv.DepthStencil.Depth = 1.0f;
|
||||
|
||||
D3D12_HEAP_PROPERTIES hp = {};
|
||||
hp.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
hp.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
|
||||
hp.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
|
||||
hp.CreationNodeMask = 1;
|
||||
hp.VisibleNodeMask = 1;
|
||||
|
||||
D3D12_RESOURCE_DESC rd = {};
|
||||
rd.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
rd.Alignment = 0;
|
||||
rd.Width = width;
|
||||
rd.Height = height;
|
||||
rd.DepthOrArraySize = 1;
|
||||
rd.MipLevels = 0;
|
||||
rd.Format = DXGI_FORMAT_D32_FLOAT;
|
||||
rd.SampleDesc.Count = 1;
|
||||
rd.SampleDesc.Quality = 0;
|
||||
rd.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
|
||||
rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
||||
ThrowIfFailed(d3d.device->CreateCommittedResource(&hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_DEPTH_WRITE, &depth_optimized_cv, IID_PPV_ARGS(&d3d.depth_stencil_buffer)));
|
||||
|
||||
d3d.device->CreateDepthStencilView(d3d.depth_stencil_buffer.Get(), &dsv_desc, get_cpu_descriptor_handle(d3d.dsv_heap));
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_on_resize(void) {
|
||||
if (d3d.render_targets[0].Get() != nullptr) {
|
||||
d3d.render_targets[0].Reset();
|
||||
d3d.render_targets[1].Reset();
|
||||
ThrowIfFailed(d3d.swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT));
|
||||
d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex();
|
||||
create_render_target_views();
|
||||
create_depth_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_init(void ) {
|
||||
// Load d3d12.dll
|
||||
d3d.d3d12_module = LoadLibraryW(L"d3d12.dll");
|
||||
if (d3d.d3d12_module == nullptr) {
|
||||
ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "d3d12.dll could not be loaded");
|
||||
}
|
||||
d3d.D3D12CreateDevice = (PFN_D3D12_CREATE_DEVICE)GetProcAddress(d3d.d3d12_module, "D3D12CreateDevice");
|
||||
#if DEBUG_D3D
|
||||
d3d.D3D12GetDebugInterface = (PFN_D3D12_GET_DEBUG_INTERFACE)GetProcAddress(d3d.d3d12_module, "D3D12GetDebugInterface");
|
||||
#endif
|
||||
|
||||
// Load D3DCompiler_47.dll
|
||||
d3d.d3dcompiler_module = LoadLibraryW(L"D3DCompiler_47.dll");
|
||||
if (d3d.d3dcompiler_module == nullptr) {
|
||||
ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()), gfx_dxgi_get_h_wnd(), "D3DCompiler_47.dll could not be loaded");
|
||||
}
|
||||
d3d.D3DCompile = (pD3DCompile)GetProcAddress(d3d.d3dcompiler_module, "D3DCompile");
|
||||
|
||||
// Create device
|
||||
{
|
||||
UINT debug_flags = 0;
|
||||
#if DEBUG_D3D
|
||||
ComPtr<ID3D12Debug> debug_controller;
|
||||
if (SUCCEEDED(d3d.D3D12GetDebugInterface(IID_PPV_ARGS(&debug_controller)))) {
|
||||
debug_controller->EnableDebugLayer();
|
||||
debug_flags |= DXGI_CREATE_FACTORY_DEBUG;
|
||||
}
|
||||
#endif
|
||||
|
||||
gfx_dxgi_create_factory_and_device(DEBUG_D3D, 12, [](IDXGIAdapter1 *adapter, bool test_only) {
|
||||
HRESULT res = d3d.D3D12CreateDevice(
|
||||
adapter,
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
IID_ID3D12Device,
|
||||
test_only ? nullptr : IID_PPV_ARGS_Helper(&d3d.device));
|
||||
|
||||
if (test_only) {
|
||||
return SUCCEEDED(res);
|
||||
} else {
|
||||
ThrowIfFailed(res, gfx_dxgi_get_h_wnd(), "Failed to create D3D12 device.");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Create command queues
|
||||
{
|
||||
D3D12_COMMAND_QUEUE_DESC queue_desc = {};
|
||||
queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
|
||||
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
ThrowIfFailed(d3d.device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&d3d.command_queue)));
|
||||
}
|
||||
{
|
||||
D3D12_COMMAND_QUEUE_DESC queue_desc = {};
|
||||
queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
|
||||
queue_desc.Type = D3D12_COMMAND_LIST_TYPE_COPY;
|
||||
ThrowIfFailed(d3d.device->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&d3d.copy_command_queue)));
|
||||
}
|
||||
|
||||
// Create swap chain
|
||||
{
|
||||
ComPtr<IDXGISwapChain1> swap_chain1 = gfx_dxgi_create_swap_chain(d3d.command_queue.Get());
|
||||
ThrowIfFailed(swap_chain1->QueryInterface(__uuidof(IDXGISwapChain3), &d3d.swap_chain));
|
||||
d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex();
|
||||
}
|
||||
|
||||
// Create render target views
|
||||
{
|
||||
D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = {};
|
||||
rtv_heap_desc.NumDescriptors = 2;
|
||||
rtv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
|
||||
rtv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
||||
ThrowIfFailed(d3d.device->CreateDescriptorHeap(&rtv_heap_desc, IID_PPV_ARGS(&d3d.rtv_heap)));
|
||||
d3d.rtv_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
|
||||
|
||||
create_render_target_views();
|
||||
}
|
||||
|
||||
// Create Z-buffer
|
||||
{
|
||||
D3D12_DESCRIPTOR_HEAP_DESC dsv_heap_desc = {};
|
||||
dsv_heap_desc.NumDescriptors = 1;
|
||||
dsv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
|
||||
dsv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
||||
ThrowIfFailed(d3d.device->CreateDescriptorHeap(&dsv_heap_desc, IID_PPV_ARGS(&d3d.dsv_heap)));
|
||||
|
||||
create_depth_buffer();
|
||||
}
|
||||
|
||||
// Create SRV heap for texture descriptors
|
||||
{
|
||||
D3D12_DESCRIPTOR_HEAP_DESC srv_heap_desc = {};
|
||||
srv_heap_desc.NumDescriptors = 1024; // Max unique textures per frame
|
||||
srv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
||||
srv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||
ThrowIfFailed(d3d.device->CreateDescriptorHeap(&srv_heap_desc, IID_PPV_ARGS(&d3d.srv_heap)));
|
||||
d3d.srv_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||
}
|
||||
|
||||
// Create sampler heap and descriptors
|
||||
{
|
||||
D3D12_DESCRIPTOR_HEAP_DESC sampler_heap_desc = {};
|
||||
sampler_heap_desc.NumDescriptors = 18;
|
||||
sampler_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
|
||||
sampler_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||
ThrowIfFailed(d3d.device->CreateDescriptorHeap(&sampler_heap_desc, IID_PPV_ARGS(&d3d.sampler_heap)));
|
||||
d3d.sampler_descriptor_size = d3d.device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
|
||||
|
||||
static const D3D12_TEXTURE_ADDRESS_MODE address_modes[] = {
|
||||
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
|
||||
D3D12_TEXTURE_ADDRESS_MODE_MIRROR,
|
||||
D3D12_TEXTURE_ADDRESS_MODE_CLAMP
|
||||
};
|
||||
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE sampler_handle = get_cpu_descriptor_handle(d3d.sampler_heap);
|
||||
int pos = 0;
|
||||
for (int linear_filter = 0; linear_filter < 2; linear_filter++) {
|
||||
for (int cms = 0; cms < 3; cms++) {
|
||||
for (int cmt = 0; cmt < 3; cmt++) {
|
||||
D3D12_SAMPLER_DESC sampler_desc = {};
|
||||
sampler_desc.Filter = linear_filter ? D3D12_FILTER_MIN_MAG_MIP_LINEAR : D3D12_FILTER_MIN_MAG_MIP_POINT;
|
||||
sampler_desc.AddressU = address_modes[cms];
|
||||
sampler_desc.AddressV = address_modes[cmt];
|
||||
sampler_desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
|
||||
sampler_desc.MinLOD = 0;
|
||||
sampler_desc.MaxLOD = D3D12_FLOAT32_MAX;
|
||||
sampler_desc.MipLODBias = 0.0f;
|
||||
sampler_desc.MaxAnisotropy = 1;
|
||||
sampler_desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
|
||||
d3d.device->CreateSampler(&sampler_desc, CD3DX12_CPU_DESCRIPTOR_HANDLE(sampler_handle, pos++, d3d.sampler_descriptor_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create constant buffer view for noise
|
||||
{
|
||||
/*D3D12_DESCRIPTOR_HEAP_DESC cbv_heap_desc = {};
|
||||
cbv_heap_desc.NumDescriptors = 1;
|
||||
cbv_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
||||
srv_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||
ThrowIfFailed(d3d.device->CreateDescriptorHeap*/
|
||||
|
||||
CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD);
|
||||
CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(256);
|
||||
ThrowIfFailed(d3d.device->CreateCommittedResource(
|
||||
&hp,
|
||||
D3D12_HEAP_FLAG_NONE,
|
||||
&rdb,
|
||||
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||
nullptr,
|
||||
IID_PPV_ARGS(&d3d.noise_cb)));
|
||||
|
||||
CD3DX12_RANGE read_range(0, 0); // Read not possible from CPU
|
||||
ThrowIfFailed(d3d.noise_cb->Map(0, &read_range, &d3d.mapped_noise_cb_address));
|
||||
}
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&d3d.command_allocator)));
|
||||
ThrowIfFailed(d3d.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COPY, IID_PPV_ARGS(&d3d.copy_command_allocator)));
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, d3d.command_allocator.Get(), nullptr, IID_PPV_ARGS(&d3d.command_list)));
|
||||
ThrowIfFailed(d3d.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_COPY, d3d.copy_command_allocator.Get(), nullptr, IID_PPV_ARGS(&d3d.copy_command_list)));
|
||||
|
||||
ThrowIfFailed(d3d.command_list->Close());
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&d3d.fence)));
|
||||
d3d.fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
if (d3d.fence_event == nullptr) {
|
||||
ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
|
||||
}
|
||||
|
||||
ThrowIfFailed(d3d.device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&d3d.copy_fence)));
|
||||
|
||||
{
|
||||
// Create a buffer of 1 MB in size. With a 120 star speed run 192 kB seems to be max usage.
|
||||
CD3DX12_HEAP_PROPERTIES hp(D3D12_HEAP_TYPE_UPLOAD);
|
||||
CD3DX12_RESOURCE_DESC rdb = CD3DX12_RESOURCE_DESC::Buffer(256 * 1024 * sizeof(float));
|
||||
ThrowIfFailed(d3d.device->CreateCommittedResource(
|
||||
&hp,
|
||||
D3D12_HEAP_FLAG_NONE,
|
||||
&rdb,
|
||||
D3D12_RESOURCE_STATE_GENERIC_READ,
|
||||
nullptr,
|
||||
IID_PPV_ARGS(&d3d.vertex_buffer)));
|
||||
|
||||
CD3DX12_RANGE read_range(0, 0); // Read not possible from CPU
|
||||
ThrowIfFailed(d3d.vertex_buffer->Map(0, &read_range, &d3d.mapped_vbuf_address));
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_end_frame(void) {
|
||||
if (max_texture_uploads < texture_uploads && texture_uploads != 38 && texture_uploads != 34 && texture_uploads != 29) {
|
||||
max_texture_uploads = texture_uploads;
|
||||
}
|
||||
//printf("Texture uploads: %d %d\n", max_texture_uploads, texture_uploads);
|
||||
texture_uploads = 0;
|
||||
|
||||
ThrowIfFailed(d3d.copy_command_list->Close());
|
||||
{
|
||||
ID3D12CommandList *lists[] = { d3d.copy_command_list.Get() };
|
||||
d3d.copy_command_queue->ExecuteCommandLists(1, lists);
|
||||
d3d.copy_command_queue->Signal(d3d.copy_fence.Get(), ++d3d.copy_fence_value);
|
||||
}
|
||||
|
||||
CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
|
||||
d3d.render_targets[d3d.frame_index].Get(),
|
||||
D3D12_RESOURCE_STATE_RENDER_TARGET,
|
||||
D3D12_RESOURCE_STATE_PRESENT);
|
||||
d3d.command_list->ResourceBarrier(1, &barrier);
|
||||
|
||||
d3d.command_queue->Wait(d3d.copy_fence.Get(), d3d.copy_fence_value);
|
||||
|
||||
ThrowIfFailed(d3d.command_list->Close());
|
||||
|
||||
{
|
||||
ID3D12CommandList *lists[] = { d3d.command_list.Get() };
|
||||
d3d.command_queue->ExecuteCommandLists(1, lists);
|
||||
}
|
||||
|
||||
{
|
||||
LARGE_INTEGER t0;
|
||||
QueryPerformanceCounter(&t0);
|
||||
//printf("Present: %llu %u\n", (unsigned long long)(t0.QuadPart - d3d.qpc_init), d3d.length_in_vsync_frames);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_direct3d12_finish_render(void) {
|
||||
LARGE_INTEGER t0, t1, t2;
|
||||
QueryPerformanceCounter(&t0);
|
||||
|
||||
static UINT64 fence_value;
|
||||
ThrowIfFailed(d3d.command_queue->Signal(d3d.fence.Get(), ++fence_value));
|
||||
if (d3d.fence->GetCompletedValue() < fence_value) {
|
||||
ThrowIfFailed(d3d.fence->SetEventOnCompletion(fence_value, d3d.fence_event));
|
||||
WaitForSingleObject(d3d.fence_event, INFINITE);
|
||||
}
|
||||
QueryPerformanceCounter(&t1);
|
||||
|
||||
d3d.resources_to_clean_at_end_of_frame.clear();
|
||||
for (std::pair<size_t, ComPtr<ID3D12Resource>>& heap : d3d.upload_heaps_in_flight) {
|
||||
d3d.upload_heaps[heap.first].push_back(std::move(heap.second));
|
||||
}
|
||||
d3d.upload_heaps_in_flight.clear();
|
||||
for (std::pair<struct TextureHeap *, uint8_t>& item : d3d.texture_heap_allocations_to_reclaim_at_end_of_frame) {
|
||||
item.first->free_list.push_back(item.second);
|
||||
}
|
||||
d3d.texture_heap_allocations_to_reclaim_at_end_of_frame.clear();
|
||||
|
||||
QueryPerformanceCounter(&t2);
|
||||
|
||||
d3d.frame_index = d3d.swap_chain->GetCurrentBackBufferIndex();
|
||||
|
||||
ThrowIfFailed(d3d.copy_command_allocator->Reset());
|
||||
ThrowIfFailed(d3d.copy_command_list->Reset(d3d.copy_command_allocator.Get(), nullptr));
|
||||
|
||||
//printf("done %llu gpu:%d wait:%d freed:%llu frame:%u %u monitor:%u t:%llu\n", (unsigned long long)(t0.QuadPart - d3d.qpc_init), (int)(t1.QuadPart - t0.QuadPart), (int)(t2.QuadPart - t0.QuadPart), (unsigned long long)(t2.QuadPart - d3d.qpc_init), d3d.pending_frame_stats.rbegin()->first, stats.PresentCount, stats.SyncRefreshCount, (unsigned long long)(stats.SyncQPCTime.QuadPart - d3d.qpc_init));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct GfxRenderingAPI gfx_direct3d12_api = {
|
||||
gfx_direct3d12_z_is_from_0_to_1,
|
||||
gfx_direct3d12_unload_shader,
|
||||
gfx_direct3d12_load_shader,
|
||||
gfx_direct3d12_create_and_load_new_shader,
|
||||
gfx_direct3d12_lookup_shader,
|
||||
gfx_direct3d12_shader_get_info,
|
||||
gfx_direct3d12_new_texture,
|
||||
gfx_direct3d12_select_texture,
|
||||
gfx_direct3d12_upload_texture,
|
||||
gfx_direct3d12_set_sampler_parameters,
|
||||
gfx_direct3d12_set_depth_test,
|
||||
gfx_direct3d12_set_depth_mask,
|
||||
gfx_direct3d12_set_zmode_decal,
|
||||
gfx_direct3d12_set_viewport,
|
||||
gfx_direct3d12_set_scissor,
|
||||
gfx_direct3d12_set_use_alpha,
|
||||
gfx_direct3d12_draw_triangles,
|
||||
gfx_direct3d12_init,
|
||||
gfx_direct3d12_on_resize,
|
||||
gfx_direct3d12_start_frame,
|
||||
gfx_direct3d12_end_frame,
|
||||
gfx_direct3d12_finish_render
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifdef ENABLE_DX12
|
||||
|
||||
#ifndef GFX_DIRECT3D12_H
|
||||
#define GFX_DIRECT3D12_H
|
||||
|
||||
#include "gfx_rendering_api.h"
|
||||
|
||||
extern struct GfxRenderingAPI gfx_direct3d12_api;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef GFX_DIRECT3D12_GUIDS_H
|
||||
#define GFX_DIRECT3D12_GUIDS_H
|
||||
|
||||
#ifdef __MINGW32__
|
||||
|
||||
// This file is only needed due to missing MinGW-specific headers for d3d12.h.
|
||||
// It will define IID_* symbols having the "selectany" attribute (assuming
|
||||
// d3d12.h was earlier included), as well as make __uuidof(...) work.
|
||||
|
||||
#define DEF_GUID(type,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
|
||||
__CRT_UUID_DECL(type,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
|
||||
const GUID IID_##type = __uuidof(type)
|
||||
|
||||
DEF_GUID(ID3D12Object,0xc4fec28f,0x7966,0x4e95,0x9f,0x94,0xf4,0x31,0xcb,0x56,0xc3,0xb8);
|
||||
DEF_GUID(ID3D12DeviceChild,0x905db94b,0xa00c,0x4140,0x9d,0xf5,0x2b,0x64,0xca,0x9e,0xa3,0x57);
|
||||
DEF_GUID(ID3D12RootSignature,0xc54a6b66,0x72df,0x4ee8,0x8b,0xe5,0xa9,0x46,0xa1,0x42,0x92,0x14);
|
||||
DEF_GUID(ID3D12RootSignatureDeserializer,0x34AB647B,0x3CC8,0x46AC,0x84,0x1B,0xC0,0x96,0x56,0x45,0xC0,0x46);
|
||||
DEF_GUID(ID3D12VersionedRootSignatureDeserializer,0x7F91CE67,0x090C,0x4BB7,0xB7,0x8E,0xED,0x8F,0xF2,0xE3,0x1D,0xA0);
|
||||
DEF_GUID(ID3D12Pageable,0x63ee58fb,0x1268,0x4835,0x86,0xda,0xf0,0x08,0xce,0x62,0xf0,0xd6);
|
||||
DEF_GUID(ID3D12Heap,0x6b3b2502,0x6e51,0x45b3,0x90,0xee,0x98,0x84,0x26,0x5e,0x8d,0xf3);
|
||||
DEF_GUID(ID3D12Resource,0x696442be,0xa72e,0x4059,0xbc,0x79,0x5b,0x5c,0x98,0x04,0x0f,0xad);
|
||||
DEF_GUID(ID3D12CommandAllocator,0x6102dee4,0xaf59,0x4b09,0xb9,0x99,0xb4,0x4d,0x73,0xf0,0x9b,0x24);
|
||||
DEF_GUID(ID3D12Fence,0x0a753dcf,0xc4d8,0x4b91,0xad,0xf6,0xbe,0x5a,0x60,0xd9,0x5a,0x76);
|
||||
DEF_GUID(ID3D12Fence1,0x433685fe,0xe22b,0x4ca0,0xa8,0xdb,0xb5,0xb4,0xf4,0xdd,0x0e,0x4a);
|
||||
DEF_GUID(ID3D12PipelineState,0x765a30f3,0xf624,0x4c6f,0xa8,0x28,0xac,0xe9,0x48,0x62,0x24,0x45);
|
||||
DEF_GUID(ID3D12DescriptorHeap,0x8efb471d,0x616c,0x4f49,0x90,0xf7,0x12,0x7b,0xb7,0x63,0xfa,0x51);
|
||||
DEF_GUID(ID3D12QueryHeap,0x0d9658ae,0xed45,0x469e,0xa6,0x1d,0x97,0x0e,0xc5,0x83,0xca,0xb4);
|
||||
DEF_GUID(ID3D12CommandSignature,0xc36a797c,0xec80,0x4f0a,0x89,0x85,0xa7,0xb2,0x47,0x50,0x82,0xd1);
|
||||
DEF_GUID(ID3D12CommandList,0x7116d91c,0xe7e4,0x47ce,0xb8,0xc6,0xec,0x81,0x68,0xf4,0x37,0xe5);
|
||||
DEF_GUID(ID3D12GraphicsCommandList,0x5b160d0f,0xac1b,0x4185,0x8b,0xa8,0xb3,0xae,0x42,0xa5,0xa4,0x55);
|
||||
DEF_GUID(ID3D12GraphicsCommandList1,0x553103fb,0x1fe7,0x4557,0xbb,0x38,0x94,0x6d,0x7d,0x0e,0x7c,0xa7);
|
||||
DEF_GUID(ID3D12GraphicsCommandList2,0x38C3E585,0xFF17,0x412C,0x91,0x50,0x4F,0xC6,0xF9,0xD7,0x2A,0x28);
|
||||
DEF_GUID(ID3D12CommandQueue,0x0ec870a6,0x5d7e,0x4c22,0x8c,0xfc,0x5b,0xaa,0xe0,0x76,0x16,0xed);
|
||||
DEF_GUID(ID3D12Device,0x189819f1,0x1db6,0x4b57,0xbe,0x54,0x18,0x21,0x33,0x9b,0x85,0xf7);
|
||||
DEF_GUID(ID3D12PipelineLibrary,0xc64226a8,0x9201,0x46af,0xb4,0xcc,0x53,0xfb,0x9f,0xf7,0x41,0x4f);
|
||||
DEF_GUID(ID3D12PipelineLibrary1,0x80eabf42,0x2568,0x4e5e,0xbd,0x82,0xc3,0x7f,0x86,0x96,0x1d,0xc3);
|
||||
DEF_GUID(ID3D12Device1,0x77acce80,0x638e,0x4e65,0x88,0x95,0xc1,0xf2,0x33,0x86,0x86,0x3e);
|
||||
DEF_GUID(ID3D12Device2,0x30baa41e,0xb15b,0x475c,0xa0,0xbb,0x1a,0xf5,0xc5,0xb6,0x43,0x28);
|
||||
DEF_GUID(ID3D12Device3,0x81dadc15,0x2bad,0x4392,0x93,0xc5,0x10,0x13,0x45,0xc4,0xaa,0x98);
|
||||
DEF_GUID(ID3D12ProtectedSession,0xA1533D18,0x0AC1,0x4084,0x85,0xB9,0x89,0xA9,0x61,0x16,0x80,0x6B);
|
||||
DEF_GUID(ID3D12ProtectedResourceSession,0x6CD696F4,0xF289,0x40CC,0x80,0x91,0x5A,0x6C,0x0A,0x09,0x9C,0x3D);
|
||||
DEF_GUID(ID3D12Device4,0xe865df17,0xa9ee,0x46f9,0xa4,0x63,0x30,0x98,0x31,0x5a,0xa2,0xe5);
|
||||
DEF_GUID(ID3D12LifetimeOwner,0xe667af9f,0xcd56,0x4f46,0x83,0xce,0x03,0x2e,0x59,0x5d,0x70,0xa8);
|
||||
DEF_GUID(ID3D12SwapChainAssistant,0xf1df64b6,0x57fd,0x49cd,0x88,0x07,0xc0,0xeb,0x88,0xb4,0x5c,0x8f);
|
||||
DEF_GUID(ID3D12LifetimeTracker,0x3fd03d36,0x4eb1,0x424a,0xa5,0x82,0x49,0x4e,0xcb,0x8b,0xa8,0x13);
|
||||
DEF_GUID(ID3D12StateObject,0x47016943,0xfca8,0x4594,0x93,0xea,0xaf,0x25,0x8b,0x55,0x34,0x6d);
|
||||
DEF_GUID(ID3D12StateObjectProperties,0xde5fa827,0x9bf9,0x4f26,0x89,0xff,0xd7,0xf5,0x6f,0xde,0x38,0x60);
|
||||
DEF_GUID(ID3D12Device5,0x8b4f173b,0x2fea,0x4b80,0x8f,0x58,0x43,0x07,0x19,0x1a,0xb9,0x5d);
|
||||
DEF_GUID(ID3D12DeviceRemovedExtendedDataSettings,0x82BC481C,0x6B9B,0x4030,0xAE,0xDB,0x7E,0xE3,0xD1,0xDF,0x1E,0x63);
|
||||
DEF_GUID(ID3D12DeviceRemovedExtendedData,0x98931D33,0x5AE8,0x4791,0xAA,0x3C,0x1A,0x73,0xA2,0x93,0x4E,0x71);
|
||||
DEF_GUID(ID3D12Device6,0xc70b221b,0x40e4,0x4a17,0x89,0xaf,0x02,0x5a,0x07,0x27,0xa6,0xdc);
|
||||
DEF_GUID(ID3D12Resource1,0x9D5E227A,0x4430,0x4161,0x88,0xB3,0x3E,0xCA,0x6B,0xB1,0x6E,0x19);
|
||||
DEF_GUID(ID3D12Heap1,0x572F7389,0x2168,0x49E3,0x96,0x93,0xD6,0xDF,0x58,0x71,0xBF,0x6D);
|
||||
DEF_GUID(ID3D12GraphicsCommandList3,0x6FDA83A7,0xB84C,0x4E38,0x9A,0xC8,0xC7,0xBD,0x22,0x01,0x6B,0x3D);
|
||||
DEF_GUID(ID3D12MetaCommand,0xDBB84C27,0x36CE,0x4FC9,0xB8,0x01,0xF0,0x48,0xC4,0x6A,0xC5,0x70);
|
||||
DEF_GUID(ID3D12GraphicsCommandList4,0x8754318e,0xd3a9,0x4541,0x98,0xcf,0x64,0x5b,0x50,0xdc,0x48,0x74);
|
||||
DEF_GUID(ID3D12Tools,0x7071e1f0,0xe84b,0x4b33,0x97,0x4f,0x12,0xfa,0x49,0xde,0x65,0xc5);
|
||||
DEF_GUID(ID3D12GraphicsCommandList5,0x55050859,0x4024,0x474c,0x87,0xf5,0x64,0x72,0xea,0xee,0x44,0xea);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,313 @@
|
||||
#if defined(ENABLE_DX11) || defined(ENABLE_DX12)
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "gfx_direct3d_common.h"
|
||||
#include "gfx_cc.h"
|
||||
|
||||
void get_cc_features(uint32_t shader_id, CCFeatures *cc_features) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
cc_features->c[0][i] = (shader_id >> (i * 3)) & 7;
|
||||
cc_features->c[1][i] = (shader_id >> (12 + i * 3)) & 7;
|
||||
}
|
||||
|
||||
cc_features->opt_alpha = (shader_id & SHADER_OPT_ALPHA) != 0;
|
||||
cc_features->opt_fog = (shader_id & SHADER_OPT_FOG) != 0;
|
||||
cc_features->opt_texture_edge = (shader_id & SHADER_OPT_TEXTURE_EDGE) != 0;
|
||||
cc_features->opt_noise = (shader_id & SHADER_OPT_NOISE) != 0;
|
||||
|
||||
cc_features->used_textures[0] = false;
|
||||
cc_features->used_textures[1] = false;
|
||||
cc_features->num_inputs = 0;
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
if (cc_features->c[i][j] >= SHADER_INPUT_1 && cc_features->c[i][j] <= SHADER_INPUT_4) {
|
||||
if (cc_features->c[i][j] > cc_features->num_inputs) {
|
||||
cc_features->num_inputs = cc_features->c[i][j];
|
||||
}
|
||||
}
|
||||
if (cc_features->c[i][j] == SHADER_TEXEL0 || cc_features->c[i][j] == SHADER_TEXEL0A) {
|
||||
cc_features->used_textures[0] = true;
|
||||
}
|
||||
if (cc_features->c[i][j] == SHADER_TEXEL1) {
|
||||
cc_features->used_textures[1] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cc_features->do_single[0] = cc_features->c[0][2] == 0;
|
||||
cc_features->do_single[1] = cc_features->c[1][2] == 0;
|
||||
cc_features->do_multiply[0] = cc_features->c[0][1] == 0 && cc_features->c[0][3] == 0;
|
||||
cc_features->do_multiply[1] = cc_features->c[1][1] == 0 && cc_features->c[1][3] == 0;
|
||||
cc_features->do_mix[0] = cc_features->c[0][1] == cc_features->c[0][3];
|
||||
cc_features->do_mix[1] = cc_features->c[1][1] == cc_features->c[1][3];
|
||||
cc_features->color_alpha_same = (shader_id & 0xfff) == ((shader_id >> 12) & 0xfff);
|
||||
}
|
||||
|
||||
static void append_str(char *buf, size_t *len, const char *str) {
|
||||
while (*str != '\0') buf[(*len)++] = *str++;
|
||||
}
|
||||
|
||||
static void append_line(char *buf, size_t *len, const char *str) {
|
||||
while (*str != '\0') buf[(*len)++] = *str++;
|
||||
buf[(*len)++] = '\r';
|
||||
buf[(*len)++] = '\n';
|
||||
}
|
||||
|
||||
static const char *shader_item_to_str(uint32_t item, bool with_alpha, bool only_alpha, bool inputs_have_alpha, bool hint_single_element) {
|
||||
if (!only_alpha) {
|
||||
switch (item) {
|
||||
default:
|
||||
case SHADER_0:
|
||||
return with_alpha ? "float4(0.0, 0.0, 0.0, 0.0)" : "float3(0.0, 0.0, 0.0)";
|
||||
case SHADER_INPUT_1:
|
||||
return with_alpha || !inputs_have_alpha ? "input.input1" : "input.input1.rgb";
|
||||
case SHADER_INPUT_2:
|
||||
return with_alpha || !inputs_have_alpha ? "input.input2" : "input.input2.rgb";
|
||||
case SHADER_INPUT_3:
|
||||
return with_alpha || !inputs_have_alpha ? "input.input3" : "input.input3.rgb";
|
||||
case SHADER_INPUT_4:
|
||||
return with_alpha || !inputs_have_alpha ? "input.input4" : "input.input4.rgb";
|
||||
case SHADER_TEXEL0:
|
||||
return with_alpha ? "texVal0" : "texVal0.rgb";
|
||||
case SHADER_TEXEL0A:
|
||||
return hint_single_element ? "texVal0.a" : (with_alpha ? "float4(texVal0.a, texVal0.a, texVal0.a, texVal0.a)" : "float3(texVal0.a, texVal0.a, texVal0.a)");
|
||||
case SHADER_TEXEL1:
|
||||
return with_alpha ? "texVal1" : "texVal1.rgb";
|
||||
}
|
||||
} else {
|
||||
switch (item) {
|
||||
default:
|
||||
case SHADER_0:
|
||||
return "0.0";
|
||||
case SHADER_INPUT_1:
|
||||
return "input.input1.a";
|
||||
case SHADER_INPUT_2:
|
||||
return "input.input2.a";
|
||||
case SHADER_INPUT_3:
|
||||
return "input.input3.a";
|
||||
case SHADER_INPUT_4:
|
||||
return "input.input4.a";
|
||||
case SHADER_TEXEL0:
|
||||
return "texVal0.a";
|
||||
case SHADER_TEXEL0A:
|
||||
return "texVal0.a";
|
||||
case SHADER_TEXEL1:
|
||||
return "texVal1.a";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void append_formula(char *buf, size_t *len, const uint8_t c[2][4], bool do_single, bool do_multiply, bool do_mix, bool with_alpha, bool only_alpha, bool opt_alpha) {
|
||||
if (do_single) {
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false));
|
||||
} else if (do_multiply) {
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, " * ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
|
||||
} else if (do_mix) {
|
||||
append_str(buf, len, "lerp(");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, ", ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, ", ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
|
||||
append_str(buf, len, ")");
|
||||
} else {
|
||||
append_str(buf, len, "(");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, " - ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, ") * ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
|
||||
append_str(buf, len, " + ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false));
|
||||
}
|
||||
}
|
||||
|
||||
void gfx_direct3d_common_build_shader(char buf[4096], size_t& len, size_t& num_floats, const CCFeatures& cc_features, bool include_root_signature, bool three_point_filtering) {
|
||||
len = 0;
|
||||
num_floats = 4;
|
||||
|
||||
// Pixel shader input struct
|
||||
|
||||
if (include_root_signature) {
|
||||
append_str(buf, &len, "#define RS \"RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | DENY_VERTEX_SHADER_ROOT_ACCESS)");
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
append_str(buf, &len, ",CBV(b0, visibility = SHADER_VISIBILITY_PIXEL)");
|
||||
}
|
||||
if (cc_features.used_textures[0]) {
|
||||
append_str(buf, &len, ",DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL)");
|
||||
append_str(buf, &len, ",DescriptorTable(Sampler(s0), visibility = SHADER_VISIBILITY_PIXEL)");
|
||||
}
|
||||
if (cc_features.used_textures[1]) {
|
||||
append_str(buf, &len, ",DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL)");
|
||||
append_str(buf, &len, ",DescriptorTable(Sampler(s1), visibility = SHADER_VISIBILITY_PIXEL)");
|
||||
}
|
||||
append_line(buf, &len, "\"");
|
||||
}
|
||||
|
||||
append_line(buf, &len, "struct PSInput {");
|
||||
append_line(buf, &len, " float4 position : SV_POSITION;");
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
append_line(buf, &len, " float2 uv : TEXCOORD;");
|
||||
num_floats += 2;
|
||||
}
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
append_line(buf, &len, " float4 screenPos : TEXCOORD1;");
|
||||
}
|
||||
if (cc_features.opt_fog) {
|
||||
append_line(buf, &len, " float4 fog : FOG;");
|
||||
num_floats += 4;
|
||||
}
|
||||
for (int i = 0; i < cc_features.num_inputs; i++) {
|
||||
len += sprintf(buf + len, " float%d input%d : INPUT%d;\r\n", cc_features.opt_alpha ? 4 : 3, i + 1, i);
|
||||
num_floats += cc_features.opt_alpha ? 4 : 3;
|
||||
}
|
||||
append_line(buf, &len, "};");
|
||||
|
||||
// Textures and samplers
|
||||
|
||||
if (cc_features.used_textures[0]) {
|
||||
append_line(buf, &len, "Texture2D g_texture0 : register(t0);");
|
||||
append_line(buf, &len, "SamplerState g_sampler0 : register(s0);");
|
||||
}
|
||||
if (cc_features.used_textures[1]) {
|
||||
append_line(buf, &len, "Texture2D g_texture1 : register(t1);");
|
||||
append_line(buf, &len, "SamplerState g_sampler1 : register(s1);");
|
||||
}
|
||||
|
||||
// Constant buffer and random function
|
||||
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
append_line(buf, &len, "cbuffer PerFrameCB : register(b0) {");
|
||||
append_line(buf, &len, " uint noise_frame;");
|
||||
append_line(buf, &len, " float2 noise_scale;");
|
||||
append_line(buf, &len, "}");
|
||||
|
||||
append_line(buf, &len, "float random(in float3 value) {");
|
||||
append_line(buf, &len, " float random = dot(value, float3(12.9898, 78.233, 37.719));");
|
||||
append_line(buf, &len, " return frac(sin(random) * 143758.5453);");
|
||||
append_line(buf, &len, "}");
|
||||
}
|
||||
|
||||
// 3 point texture filtering
|
||||
// Original author: ArthurCarvalho
|
||||
// Based on GLSL implementation by twinaphex, mupen64plus-libretro project.
|
||||
|
||||
if (three_point_filtering && (cc_features.used_textures[0] || cc_features.used_textures[1])) {
|
||||
append_line(buf, &len, "cbuffer PerDrawCB : register(b1) {");
|
||||
append_line(buf, &len, " struct {");
|
||||
append_line(buf, &len, " uint width;");
|
||||
append_line(buf, &len, " uint height;");
|
||||
append_line(buf, &len, " bool linear_filtering;");
|
||||
append_line(buf, &len, " } textures[2];");
|
||||
append_line(buf, &len, "}");
|
||||
append_line(buf, &len, "#define TEX_OFFSET(tex, tSampler, texCoord, off, texSize) tex.Sample(tSampler, texCoord - off / texSize)");
|
||||
append_line(buf, &len, "float4 tex2D3PointFilter(in Texture2D tex, in SamplerState tSampler, in float2 texCoord, in float2 texSize) {");
|
||||
append_line(buf, &len, " float2 offset = frac(texCoord * texSize - float2(0.5, 0.5));");
|
||||
append_line(buf, &len, " offset -= step(1.0, offset.x + offset.y);");
|
||||
append_line(buf, &len, " float4 c0 = TEX_OFFSET(tex, tSampler, texCoord, offset, texSize);");
|
||||
append_line(buf, &len, " float4 c1 = TEX_OFFSET(tex, tSampler, texCoord, float2(offset.x - sign(offset.x), offset.y), texSize);");
|
||||
append_line(buf, &len, " float4 c2 = TEX_OFFSET(tex, tSampler, texCoord, float2(offset.x, offset.y - sign(offset.y)), texSize);");
|
||||
append_line(buf, &len, " return c0 + abs(offset.x)*(c1-c0) + abs(offset.y)*(c2-c0);");
|
||||
append_line(buf, &len, "}");
|
||||
}
|
||||
|
||||
// Vertex shader
|
||||
|
||||
append_str(buf, &len, "PSInput VSMain(float4 position : POSITION");
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
append_str(buf, &len, ", float2 uv : TEXCOORD");
|
||||
}
|
||||
if (cc_features.opt_fog) {
|
||||
append_str(buf, &len, ", float4 fog : FOG");
|
||||
}
|
||||
for (int i = 0; i < cc_features.num_inputs; i++) {
|
||||
len += sprintf(buf + len, ", float%d input%d : INPUT%d", cc_features.opt_alpha ? 4 : 3, i + 1, i);
|
||||
}
|
||||
append_line(buf, &len, ") {");
|
||||
append_line(buf, &len, " PSInput result;");
|
||||
append_line(buf, &len, " result.position = position;");
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
append_line(buf, &len, " result.screenPos = position;");
|
||||
}
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
append_line(buf, &len, " result.uv = uv;");
|
||||
}
|
||||
if (cc_features.opt_fog) {
|
||||
append_line(buf, &len, " result.fog = fog;");
|
||||
}
|
||||
for (int i = 0; i < cc_features.num_inputs; i++) {
|
||||
len += sprintf(buf + len, " result.input%d = input%d;\r\n", i + 1, i + 1);
|
||||
}
|
||||
append_line(buf, &len, " return result;");
|
||||
append_line(buf, &len, "}");
|
||||
|
||||
// Pixel shader
|
||||
if (include_root_signature) {
|
||||
append_line(buf, &len, "[RootSignature(RS)]");
|
||||
}
|
||||
append_line(buf, &len, "float4 PSMain(PSInput input) : SV_TARGET {");
|
||||
if (cc_features.used_textures[0]) {
|
||||
if (three_point_filtering) {
|
||||
append_line(buf, &len, " float4 texVal0;");
|
||||
append_line(buf, &len, " if (textures[0].linear_filtering)");
|
||||
append_line(buf, &len, " texVal0 = tex2D3PointFilter(g_texture0, g_sampler0, input.uv, float2(textures[0].width, textures[0].height));");
|
||||
append_line(buf, &len, " else");
|
||||
append_line(buf, &len, " texVal0 = g_texture0.Sample(g_sampler0, input.uv);");
|
||||
} else {
|
||||
append_line(buf, &len, " float4 texVal0 = g_texture0.Sample(g_sampler0, input.uv);");
|
||||
}
|
||||
}
|
||||
if (cc_features.used_textures[1]) {
|
||||
if (three_point_filtering) {
|
||||
append_line(buf, &len, " float4 texVal1;");
|
||||
append_line(buf, &len, " if (textures[1].linear_filtering)");
|
||||
append_line(buf, &len, " texVal1 = tex2D3PointFilter(g_texture1, g_sampler1, input.uv, float2(textures[1].width, textures[1].height));");
|
||||
append_line(buf, &len, " else");
|
||||
append_line(buf, &len, " texVal1 = g_texture1.Sample(g_sampler1, input.uv);");
|
||||
} else {
|
||||
append_line(buf, &len, " float4 texVal1 = g_texture1.Sample(g_sampler1, input.uv);");
|
||||
}
|
||||
}
|
||||
|
||||
append_str(buf, &len, cc_features.opt_alpha ? " float4 texel = " : " float3 texel = ");
|
||||
if (!cc_features.color_alpha_same && cc_features.opt_alpha) {
|
||||
append_str(buf, &len, "float4(");
|
||||
append_formula(buf, &len, cc_features.c, cc_features.do_single[0], cc_features.do_multiply[0], cc_features.do_mix[0], false, false, true);
|
||||
append_str(buf, &len, ", ");
|
||||
append_formula(buf, &len, cc_features.c, cc_features.do_single[1], cc_features.do_multiply[1], cc_features.do_mix[1], true, true, true);
|
||||
append_str(buf, &len, ")");
|
||||
} else {
|
||||
append_formula(buf, &len, cc_features.c, cc_features.do_single[0], cc_features.do_multiply[0], cc_features.do_mix[0], cc_features.opt_alpha, false, cc_features.opt_alpha);
|
||||
}
|
||||
append_line(buf, &len, ";");
|
||||
|
||||
if (cc_features.opt_texture_edge && cc_features.opt_alpha) {
|
||||
append_line(buf, &len, " if (texel.a > 0.3) texel.a = 1.0; else discard;");
|
||||
}
|
||||
// TODO discard if alpha is 0?
|
||||
if (cc_features.opt_fog) {
|
||||
if (cc_features.opt_alpha) {
|
||||
append_line(buf, &len, " texel = float4(lerp(texel.rgb, input.fog.rgb, input.fog.a), texel.a);");
|
||||
} else {
|
||||
append_line(buf, &len, " texel = lerp(texel, input.fog.rgb, input.fog.a);");
|
||||
}
|
||||
}
|
||||
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
append_line(buf, &len, " float2 coords = (input.screenPos.xy / input.screenPos.w) * noise_scale;");
|
||||
append_line(buf, &len, " texel.a *= round(random(float3(floor(coords), noise_frame)));");
|
||||
}
|
||||
|
||||
if (cc_features.opt_alpha) {
|
||||
append_line(buf, &len, " return texel;");
|
||||
} else {
|
||||
append_line(buf, &len, " return float4(texel, 1.0);");
|
||||
}
|
||||
append_line(buf, &len, "}");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
#if defined(ENABLE_DX11) || defined(ENABLE_DX12)
|
||||
|
||||
#ifndef GFX_DIRECT3D_COMMON_H
|
||||
#define GFX_DIRECT3D_COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "gfx_cc.h"
|
||||
|
||||
void gfx_direct3d_common_build_shader(char buf[4096], size_t& len, size_t& num_floats, const CCFeatures& cc_features, bool include_root_signature, bool three_point_filtering);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,192 @@
|
||||
#ifdef ENABLE_GFX_DUMMY
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "gfx_window_manager_api.h"
|
||||
#include "gfx_rendering_api.h"
|
||||
|
||||
static void gfx_dummy_wm_init(const char *game_name, bool start_in_fullscreen) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_set_fullscreen(bool enable) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_main_loop(void (*run_one_game_iter)(void)) {
|
||||
while (1) {
|
||||
run_one_game_iter();
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_get_dimensions(uint32_t *width, uint32_t *height) {
|
||||
*width = 320;
|
||||
*height = 240;
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_handle_events(void) {
|
||||
}
|
||||
|
||||
static bool gfx_dummy_wm_start_frame(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_swap_buffers_begin(void) {
|
||||
}
|
||||
|
||||
static struct timespec gfx_dummy_wm_timediff(struct timespec t1, struct timespec t2) {
|
||||
t1.tv_sec -= t2.tv_sec;
|
||||
t1.tv_nsec -= t2.tv_nsec;
|
||||
if (t1.tv_nsec < 0) {
|
||||
t1.tv_nsec += 1000000000;
|
||||
t1.tv_sec -= 1;
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
static struct timespec gfx_dummy_wm_timeadd(struct timespec t1, struct timespec t2) {
|
||||
t1.tv_sec += t2.tv_sec;
|
||||
t1.tv_nsec += t2.tv_nsec;
|
||||
if (t1.tv_nsec > 1000000000) {
|
||||
t1.tv_nsec -= 1000000000;
|
||||
t1.tv_sec += 1;
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
static void gfx_dummy_wm_swap_buffers_end(void) {
|
||||
static struct timespec prev;
|
||||
struct timespec t;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||
struct timespec diff = gfx_dummy_wm_timediff(t, prev);
|
||||
if (diff.tv_sec == 0 && diff.tv_nsec < 1000000000 / 30) {
|
||||
struct timespec add = {0, 1000000000 / 30};
|
||||
struct timespec next = gfx_dummy_wm_timeadd(prev, add);
|
||||
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL) == EINTR) {
|
||||
}
|
||||
prev = next;
|
||||
} else {
|
||||
prev = t;
|
||||
}
|
||||
}
|
||||
|
||||
static double gfx_dummy_wm_get_time(void) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
static bool gfx_dummy_renderer_z_is_from_0_to_1(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_unload_shader(struct ShaderProgram *old_prg) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_load_shader(struct ShaderProgram *new_prg) {
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_dummy_renderer_create_and_load_new_shader(uint32_t shader_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_dummy_renderer_lookup_shader(uint32_t shader_id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) {
|
||||
*num_inputs = 0;
|
||||
used_textures[0] = false;
|
||||
used_textures[1] = false;
|
||||
}
|
||||
|
||||
static uint32_t gfx_dummy_renderer_new_texture(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_select_texture(int tile, uint32_t texture_id) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_upload_texture(const uint8_t *rgba32_buf, int width, int height) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_set_depth_test(bool depth_test) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_set_depth_mask(bool z_upd) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_set_zmode_decal(bool zmode_decal) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_set_viewport(int x, int y, int width, int height) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_set_scissor(int x, int y, int width, int height) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_set_use_alpha(bool use_alpha) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_init(void) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_on_resize(void) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_start_frame(void) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_end_frame(void) {
|
||||
}
|
||||
|
||||
static void gfx_dummy_renderer_finish_render(void) {
|
||||
}
|
||||
|
||||
struct GfxWindowManagerAPI gfx_dummy_wm_api = {
|
||||
gfx_dummy_wm_init,
|
||||
gfx_dummy_wm_set_keyboard_callbacks,
|
||||
gfx_dummy_wm_set_fullscreen_changed_callback,
|
||||
gfx_dummy_wm_set_fullscreen,
|
||||
gfx_dummy_wm_main_loop,
|
||||
gfx_dummy_wm_get_dimensions,
|
||||
gfx_dummy_wm_handle_events,
|
||||
gfx_dummy_wm_start_frame,
|
||||
gfx_dummy_wm_swap_buffers_begin,
|
||||
gfx_dummy_wm_swap_buffers_end,
|
||||
gfx_dummy_wm_get_time
|
||||
};
|
||||
|
||||
struct GfxRenderingAPI gfx_dummy_renderer_api = {
|
||||
gfx_dummy_renderer_z_is_from_0_to_1,
|
||||
gfx_dummy_renderer_unload_shader,
|
||||
gfx_dummy_renderer_load_shader,
|
||||
gfx_dummy_renderer_create_and_load_new_shader,
|
||||
gfx_dummy_renderer_lookup_shader,
|
||||
gfx_dummy_renderer_shader_get_info,
|
||||
gfx_dummy_renderer_new_texture,
|
||||
gfx_dummy_renderer_select_texture,
|
||||
gfx_dummy_renderer_upload_texture,
|
||||
gfx_dummy_renderer_set_sampler_parameters,
|
||||
gfx_dummy_renderer_set_depth_test,
|
||||
gfx_dummy_renderer_set_depth_mask,
|
||||
gfx_dummy_renderer_set_zmode_decal,
|
||||
gfx_dummy_renderer_set_viewport,
|
||||
gfx_dummy_renderer_set_scissor,
|
||||
gfx_dummy_renderer_set_use_alpha,
|
||||
gfx_dummy_renderer_draw_triangles,
|
||||
gfx_dummy_renderer_init,
|
||||
gfx_dummy_renderer_on_resize,
|
||||
gfx_dummy_renderer_start_frame,
|
||||
gfx_dummy_renderer_end_frame,
|
||||
gfx_dummy_renderer_finish_render
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifdef ENABLE_GFX_DUMMY
|
||||
|
||||
#ifndef GFX_DUMMY_H
|
||||
#define GFX_DUMMY_H
|
||||
|
||||
#include "gfx_rendering_api.h"
|
||||
#include "gfx_window_manager_api.h"
|
||||
|
||||
extern struct GfxRenderingAPI gfx_dummy_renderer_api;
|
||||
extern struct GfxWindowManagerAPI gfx_dummy_wm_api;
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,614 @@
|
||||
#if defined(ENABLE_DX11) || defined(ENABLE_DX12)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
#include <dxgi1_3.h>
|
||||
#include <versionhelpers.h>
|
||||
|
||||
#include <shellscalingapi.h>
|
||||
|
||||
|
||||
#ifndef _LANGUAGE_C
|
||||
#define _LANGUAGE_C
|
||||
#endif
|
||||
#include <PR/gbi.h>
|
||||
|
||||
#include "gfx_window_manager_api.h"
|
||||
#include "gfx_rendering_api.h"
|
||||
#include "gfx_direct3d_common.h"
|
||||
#include "gfx_screen_config.h"
|
||||
#include "gfx_pc.h"
|
||||
|
||||
#define DECLARE_GFX_DXGI_FUNCTIONS
|
||||
#include "gfx_dxgi.h"
|
||||
|
||||
#define WINCLASS_NAME L"N64GAME"
|
||||
#define GFX_API_NAME "DirectX"
|
||||
|
||||
#ifdef VERSION_EU
|
||||
#define FRAME_INTERVAL_US_NUMERATOR 40000
|
||||
#define FRAME_INTERVAL_US_DENOMINATOR 1
|
||||
#else
|
||||
#define FRAME_INTERVAL_US_NUMERATOR 100000
|
||||
#define FRAME_INTERVAL_US_DENOMINATOR 3
|
||||
#endif
|
||||
|
||||
using namespace Microsoft::WRL; // For ComPtr
|
||||
|
||||
static struct {
|
||||
HWND h_wnd;
|
||||
bool showing_error;
|
||||
uint32_t current_width, current_height;
|
||||
std::string game_name;
|
||||
|
||||
HMODULE dxgi_module;
|
||||
HRESULT (__stdcall *CreateDXGIFactory1)(REFIID riid, void **factory);
|
||||
HRESULT (__stdcall *CreateDXGIFactory2)(UINT flags, REFIID iid, void **factory);
|
||||
|
||||
bool process_dpi_awareness_done;
|
||||
|
||||
RECT last_window_rect;
|
||||
bool is_full_screen, last_maximized_state;
|
||||
|
||||
ComPtr<IDXGIFactory2> factory;
|
||||
ComPtr<IDXGISwapChain1> swap_chain;
|
||||
HANDLE waitable_object;
|
||||
uint64_t qpc_init, qpc_freq;
|
||||
uint64_t frame_timestamp; // in units of 1/FRAME_INTERVAL_US_DENOMINATOR microseconds
|
||||
std::map<UINT, DXGI_FRAME_STATISTICS> frame_stats;
|
||||
std::set<std::pair<UINT, UINT>> pending_frame_stats;
|
||||
bool dropped_frame;
|
||||
bool sync_interval_means_frames_to_wait;
|
||||
UINT length_in_vsync_frames;
|
||||
|
||||
void (*on_fullscreen_changed)(bool is_now_fullscreen);
|
||||
void (*run_one_game_iter)(void);
|
||||
bool (*on_key_down)(int scancode);
|
||||
bool (*on_key_up)(int scancode);
|
||||
void (*on_all_keys_up)(void);
|
||||
} dxgi;
|
||||
|
||||
static void load_dxgi_library(void) {
|
||||
dxgi.dxgi_module = LoadLibraryW(L"dxgi.dll");
|
||||
*(FARPROC *)&dxgi.CreateDXGIFactory1 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory1");
|
||||
*(FARPROC *)&dxgi.CreateDXGIFactory2 = GetProcAddress(dxgi.dxgi_module, "CreateDXGIFactory2");
|
||||
}
|
||||
|
||||
template <typename Fun>
|
||||
static void run_as_dpi_aware(Fun f) {
|
||||
// Make sure Windows 8.1 or newer doesn't upscale/downscale the rendered images.
|
||||
// This is an issue on Windows 8.1 and newer where moving around the window
|
||||
// between different monitors having different scaling settings will
|
||||
// by default result in the DirectX image will also be scaled accordingly.
|
||||
// The resulting scale factor is the curent monitor's scale factor divided by
|
||||
// the initial monitor's scale factor. Setting per-monitor aware disables scaling.
|
||||
|
||||
// On Windows 10 1607 and later, that is solved by setting the awarenenss per window,
|
||||
// which is done by using SetThreadDpiAwarenessContext before and after creating
|
||||
// any window. When the message handler runs, the corresponding context also applies.
|
||||
|
||||
// From windef.h, missing in MinGW.
|
||||
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
|
||||
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
|
||||
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
|
||||
#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)
|
||||
|
||||
DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext);
|
||||
*(FARPROC *)&SetThreadDpiAwarenessContext = GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetThreadDpiAwarenessContext");
|
||||
DPI_AWARENESS_CONTEXT old_awareness_context;
|
||||
if (SetThreadDpiAwarenessContext != nullptr) {
|
||||
old_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
} else {
|
||||
// Solution for Windows 8.1 and newer, but before Windows 10 1607.
|
||||
// SetProcessDpiAwareness must be called before any drawing related API is called.
|
||||
if (!dxgi.process_dpi_awareness_done) {
|
||||
HMODULE shcore_module = LoadLibraryW(L"SHCore.dll");
|
||||
if (shcore_module != nullptr) {
|
||||
HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value);
|
||||
*(FARPROC *)&SetProcessDpiAwareness = GetProcAddress(shcore_module, "SetProcessDpiAwareness");
|
||||
if (SetProcessDpiAwareness != nullptr) {
|
||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||
// Ignore result, will fail if already called or manifest already specifies dpi awareness.
|
||||
}
|
||||
FreeLibrary(shcore_module);
|
||||
}
|
||||
dxgi.process_dpi_awareness_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
f();
|
||||
|
||||
// Restore the old context
|
||||
if (SetThreadDpiAwarenessContext != nullptr && old_awareness_context != nullptr) {
|
||||
SetThreadDpiAwarenessContext(old_awareness_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void toggle_borderless_window_full_screen(bool enable, bool call_callback) {
|
||||
// Windows 7 + flip mode + waitable object can't go to exclusive fullscreen,
|
||||
// so do borderless instead. If DWM is enabled, this means we get one monitor
|
||||
// sync interval of latency extra. On Win 10 however (maybe Win 8 too), due to
|
||||
// "fullscreen optimizations" the latency is eliminated.
|
||||
|
||||
if (enable == dxgi.is_full_screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enable) {
|
||||
RECT r = dxgi.last_window_rect;
|
||||
|
||||
// Set in window mode with the last saved position and size
|
||||
SetWindowLongPtr(dxgi.h_wnd, GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW);
|
||||
|
||||
if (dxgi.last_maximized_state) {
|
||||
SetWindowPos(dxgi.h_wnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
|
||||
ShowWindow(dxgi.h_wnd, SW_MAXIMIZE);
|
||||
} else {
|
||||
SetWindowPos(dxgi.h_wnd, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED);
|
||||
ShowWindow(dxgi.h_wnd, SW_RESTORE);
|
||||
}
|
||||
|
||||
ShowCursor(TRUE);
|
||||
|
||||
dxgi.is_full_screen = false;
|
||||
} else {
|
||||
// Save if window is maximized or not
|
||||
WINDOWPLACEMENT window_placement;
|
||||
window_placement.length = sizeof(WINDOWPLACEMENT);
|
||||
GetWindowPlacement(dxgi.h_wnd, &window_placement);
|
||||
dxgi.last_maximized_state = window_placement.showCmd == SW_SHOWMAXIMIZED;
|
||||
|
||||
// Save window position and size if the window is not maximized
|
||||
GetWindowRect(dxgi.h_wnd, &dxgi.last_window_rect);
|
||||
|
||||
// Get in which monitor the window is
|
||||
HMONITOR h_monitor = MonitorFromWindow(dxgi.h_wnd, MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
// Get info from that monitor
|
||||
MONITORINFOEX monitor_info;
|
||||
monitor_info.cbSize = sizeof(MONITORINFOEX);
|
||||
GetMonitorInfo(h_monitor, &monitor_info);
|
||||
RECT r = monitor_info.rcMonitor;
|
||||
|
||||
// Set borderless full screen to that monitor
|
||||
SetWindowLongPtr(dxgi.h_wnd, GWL_STYLE, WS_VISIBLE | WS_POPUP);
|
||||
SetWindowPos(dxgi.h_wnd, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED);
|
||||
|
||||
ShowCursor(FALSE);
|
||||
|
||||
dxgi.is_full_screen = true;
|
||||
}
|
||||
|
||||
if (dxgi.on_fullscreen_changed != nullptr && call_callback) {
|
||||
dxgi.on_fullscreen_changed(enable);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_dxgi_on_resize(void) {
|
||||
if (dxgi.swap_chain.Get() != nullptr) {
|
||||
gfx_get_current_rendering_api()->on_resize();
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 desc1;
|
||||
ThrowIfFailed(dxgi.swap_chain->GetDesc1(&desc1));
|
||||
dxgi.current_width = desc1.Width;
|
||||
dxgi.current_height = desc1.Height;
|
||||
}
|
||||
}
|
||||
|
||||
static void onkeydown(WPARAM w_param, LPARAM l_param) {
|
||||
int key = ((l_param >> 16) & 0x1ff);
|
||||
if (dxgi.on_key_down != nullptr) {
|
||||
dxgi.on_key_down(key);
|
||||
}
|
||||
}
|
||||
static void onkeyup(WPARAM w_param, LPARAM l_param) {
|
||||
int key = ((l_param >> 16) & 0x1ff);
|
||||
if (dxgi.on_key_up != nullptr) {
|
||||
dxgi.on_key_up(key);
|
||||
}
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_param, LPARAM l_param) {
|
||||
switch (message) {
|
||||
case WM_SIZE:
|
||||
gfx_dxgi_on_resize();
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
exit(0);
|
||||
case WM_PAINT:
|
||||
if (dxgi.showing_error) {
|
||||
return DefWindowProcW(h_wnd, message, w_param, l_param);
|
||||
} else {
|
||||
if (dxgi.run_one_game_iter != nullptr) {
|
||||
dxgi.run_one_game_iter();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WM_ACTIVATEAPP:
|
||||
if (dxgi.on_all_keys_up != nullptr) {
|
||||
dxgi.on_all_keys_up();
|
||||
}
|
||||
break;
|
||||
case WM_KEYDOWN:
|
||||
onkeydown(w_param, l_param);
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
onkeyup(w_param, l_param);
|
||||
break;
|
||||
case WM_SYSKEYDOWN:
|
||||
if ((w_param == VK_RETURN) && ((l_param & 1 << 30) == 0)) {
|
||||
toggle_borderless_window_full_screen(!dxgi.is_full_screen, true);
|
||||
break;
|
||||
} else {
|
||||
return DefWindowProcW(h_wnd, message, w_param, l_param);
|
||||
}
|
||||
default:
|
||||
return DefWindowProcW(h_wnd, message, w_param, l_param);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gfx_dxgi_init(const char *game_name, bool start_in_fullscreen) {
|
||||
LARGE_INTEGER qpc_init, qpc_freq;
|
||||
QueryPerformanceCounter(&qpc_init);
|
||||
QueryPerformanceFrequency(&qpc_freq);
|
||||
dxgi.qpc_init = qpc_init.QuadPart;
|
||||
dxgi.qpc_freq = qpc_freq.QuadPart;
|
||||
|
||||
// Prepare window title
|
||||
|
||||
char title[512];
|
||||
wchar_t w_title[512];
|
||||
int len = sprintf(title, "%s (%s)", game_name, GFX_API_NAME);
|
||||
mbstowcs(w_title, title, len + 1);
|
||||
dxgi.game_name = game_name;
|
||||
|
||||
// Create window
|
||||
WNDCLASSEXW wcex;
|
||||
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
|
||||
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wcex.lpfnWndProc = gfx_dxgi_wnd_proc;
|
||||
wcex.cbClsExtra = 0;
|
||||
wcex.cbWndExtra = 0;
|
||||
wcex.hInstance = nullptr;
|
||||
wcex.hIcon = nullptr;
|
||||
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||
wcex.lpszMenuName = nullptr;
|
||||
wcex.lpszClassName = WINCLASS_NAME;
|
||||
wcex.hIconSm = nullptr;
|
||||
|
||||
ATOM winclass = RegisterClassExW(&wcex);
|
||||
|
||||
|
||||
run_as_dpi_aware([&] () {
|
||||
// We need to be dpi aware when calculating the size
|
||||
RECT wr = {0, 0, DESIRED_SCREEN_WIDTH, DESIRED_SCREEN_HEIGHT};
|
||||
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
|
||||
dxgi.h_wnd = CreateWindowW(WINCLASS_NAME, w_title, WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT, 0, wr.right - wr.left, wr.bottom - wr.top, nullptr, nullptr, nullptr, nullptr);
|
||||
});
|
||||
|
||||
load_dxgi_library();
|
||||
|
||||
ShowWindow(dxgi.h_wnd, SW_SHOW);
|
||||
UpdateWindow(dxgi.h_wnd);
|
||||
|
||||
if (start_in_fullscreen) {
|
||||
toggle_borderless_window_full_screen(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_dxgi_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
|
||||
dxgi.on_fullscreen_changed = on_fullscreen_changed;
|
||||
}
|
||||
|
||||
static void gfx_dxgi_set_fullscreen(bool enable) {
|
||||
toggle_borderless_window_full_screen(enable, true);
|
||||
}
|
||||
|
||||
static void gfx_dxgi_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) {
|
||||
dxgi.on_key_down = on_key_down;
|
||||
dxgi.on_key_up = on_key_up;
|
||||
dxgi.on_all_keys_up = on_all_keys_up;
|
||||
}
|
||||
|
||||
static void gfx_dxgi_main_loop(void (*run_one_game_iter)(void)) {
|
||||
dxgi.run_one_game_iter = run_one_game_iter;
|
||||
|
||||
MSG msg;
|
||||
while (GetMessage(&msg, nullptr, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_dxgi_get_dimensions(uint32_t *width, uint32_t *height) {
|
||||
*width = dxgi.current_width;
|
||||
*height = dxgi.current_height;
|
||||
}
|
||||
|
||||
static void gfx_dxgi_handle_events(void) {
|
||||
/*MSG msg;
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}*/
|
||||
}
|
||||
|
||||
static uint64_t qpc_to_us(uint64_t qpc) {
|
||||
return qpc / dxgi.qpc_freq * 1000000 + qpc % dxgi.qpc_freq * 1000000 / dxgi.qpc_freq;
|
||||
}
|
||||
|
||||
static bool gfx_dxgi_start_frame(void) {
|
||||
DXGI_FRAME_STATISTICS stats;
|
||||
if (dxgi.swap_chain->GetFrameStatistics(&stats) == S_OK && (stats.SyncRefreshCount != 0 || stats.SyncQPCTime.QuadPart != 0ULL)) {
|
||||
{
|
||||
LARGE_INTEGER t0;
|
||||
QueryPerformanceCounter(&t0);
|
||||
//printf("Get frame stats: %llu\n", (unsigned long long)(t0.QuadPart - dxgi.qpc_init));
|
||||
}
|
||||
//printf("stats: %u %u %u %u %u %.6f\n", dxgi.pending_frame_stats.rbegin()->first, dxgi.pending_frame_stats.rbegin()->second, stats.PresentCount, stats.PresentRefreshCount, stats.SyncRefreshCount, (double)(stats.SyncQPCTime.QuadPart - dxgi.qpc_init) / dxgi.qpc_freq);
|
||||
if (dxgi.frame_stats.empty() || dxgi.frame_stats.rbegin()->second.PresentCount != stats.PresentCount) {
|
||||
dxgi.frame_stats.insert(std::make_pair(stats.PresentCount, stats));
|
||||
}
|
||||
if (dxgi.frame_stats.size() > 3) {
|
||||
dxgi.frame_stats.erase(dxgi.frame_stats.begin());
|
||||
}
|
||||
}
|
||||
if (!dxgi.frame_stats.empty()) {
|
||||
while (!dxgi.pending_frame_stats.empty() && dxgi.pending_frame_stats.begin()->first < dxgi.frame_stats.rbegin()->first) {
|
||||
dxgi.pending_frame_stats.erase(dxgi.pending_frame_stats.begin());
|
||||
}
|
||||
}
|
||||
while (dxgi.pending_frame_stats.size() > 15) {
|
||||
// Just make sure the list doesn't grow too large if GetFrameStatistics fails.
|
||||
dxgi.pending_frame_stats.erase(dxgi.pending_frame_stats.begin());
|
||||
}
|
||||
|
||||
dxgi.frame_timestamp += FRAME_INTERVAL_US_NUMERATOR;
|
||||
|
||||
if (dxgi.frame_stats.size() >= 2) {
|
||||
DXGI_FRAME_STATISTICS *first = &dxgi.frame_stats.begin()->second;
|
||||
DXGI_FRAME_STATISTICS *last = &dxgi.frame_stats.rbegin()->second;
|
||||
uint64_t sync_qpc_diff = last->SyncQPCTime.QuadPart - first->SyncQPCTime.QuadPart;
|
||||
UINT sync_vsync_diff = last->SyncRefreshCount - first->SyncRefreshCount;
|
||||
UINT present_vsync_diff = last->PresentRefreshCount - first->PresentRefreshCount;
|
||||
UINT present_diff = last->PresentCount - first->PresentCount;
|
||||
|
||||
if (sync_vsync_diff == 0) {
|
||||
sync_vsync_diff = 1;
|
||||
}
|
||||
|
||||
double estimated_vsync_interval = (double)sync_qpc_diff / (double)sync_vsync_diff;
|
||||
uint64_t estimated_vsync_interval_us = qpc_to_us(estimated_vsync_interval);
|
||||
//printf("Estimated vsync_interval: %d\n", (int)estimated_vsync_interval_us);
|
||||
if (estimated_vsync_interval_us < 2 || estimated_vsync_interval_us > 1000000) {
|
||||
// Unreasonable, maybe a monitor change
|
||||
estimated_vsync_interval_us = 16666;
|
||||
estimated_vsync_interval = estimated_vsync_interval_us * dxgi.qpc_freq / 1000000;
|
||||
}
|
||||
|
||||
UINT queued_vsyncs = 0;
|
||||
bool is_first = true;
|
||||
for (const std::pair<UINT, UINT>& p : dxgi.pending_frame_stats) {
|
||||
if (is_first && dxgi.sync_interval_means_frames_to_wait) {
|
||||
is_first = false;
|
||||
continue;
|
||||
}
|
||||
queued_vsyncs += p.second;
|
||||
}
|
||||
|
||||
uint64_t last_frame_present_end_qpc = (last->SyncQPCTime.QuadPart - dxgi.qpc_init) + estimated_vsync_interval * queued_vsyncs;
|
||||
uint64_t last_end_us = qpc_to_us(last_frame_present_end_qpc);
|
||||
|
||||
double vsyncs_to_wait = (double)(int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) / estimated_vsync_interval_us;
|
||||
//printf("ts: %llu, last_end_us: %llu, Init v: %f\n", dxgi.frame_timestamp / 3, last_end_us, vsyncs_to_wait);
|
||||
|
||||
if (vsyncs_to_wait <= 0) {
|
||||
// Too late
|
||||
|
||||
if ((int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) < -66666) {
|
||||
// The application must have been paused or similar
|
||||
vsyncs_to_wait = round(((double)FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) / estimated_vsync_interval_us);
|
||||
if (vsyncs_to_wait < 1) {
|
||||
vsyncs_to_wait = 1;
|
||||
}
|
||||
dxgi.frame_timestamp = FRAME_INTERVAL_US_DENOMINATOR * (last_end_us + vsyncs_to_wait * estimated_vsync_interval_us);
|
||||
} else {
|
||||
// Drop frame
|
||||
//printf("Dropping frame\n");
|
||||
dxgi.dropped_frame = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (floor(vsyncs_to_wait) != vsyncs_to_wait) {
|
||||
uint64_t left = last_end_us + floor(vsyncs_to_wait) * estimated_vsync_interval_us;
|
||||
uint64_t right = last_end_us + ceil(vsyncs_to_wait) * estimated_vsync_interval_us;
|
||||
uint64_t adjusted_desired_time = dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR + (last_end_us + (FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) > dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000);
|
||||
int64_t diff_left = adjusted_desired_time - left;
|
||||
int64_t diff_right = right - adjusted_desired_time;
|
||||
if (diff_left < 0) {
|
||||
diff_left = -diff_left;
|
||||
}
|
||||
if (diff_right < 0) {
|
||||
diff_right = -diff_right;
|
||||
}
|
||||
if (diff_left < diff_right) {
|
||||
vsyncs_to_wait = floor(vsyncs_to_wait);
|
||||
} else {
|
||||
vsyncs_to_wait = ceil(vsyncs_to_wait);
|
||||
}
|
||||
if (vsyncs_to_wait == 0) {
|
||||
//printf("vsyncs_to_wait became 0 so dropping frame\n");
|
||||
dxgi.dropped_frame = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//printf("v: %d\n", (int)vsyncs_to_wait);
|
||||
if (vsyncs_to_wait > 4) {
|
||||
// Invalid, so change to 4
|
||||
vsyncs_to_wait = 4;
|
||||
}
|
||||
dxgi.length_in_vsync_frames = vsyncs_to_wait;
|
||||
} else {
|
||||
dxgi.length_in_vsync_frames = 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_dxgi_swap_buffers_begin(void) {
|
||||
//dxgi.length_in_vsync_frames = 1;
|
||||
ThrowIfFailed(dxgi.swap_chain->Present(dxgi.length_in_vsync_frames, 0));
|
||||
UINT this_present_id;
|
||||
if (dxgi.swap_chain->GetLastPresentCount(&this_present_id) == S_OK) {
|
||||
dxgi.pending_frame_stats.insert(std::make_pair(this_present_id, dxgi.length_in_vsync_frames));
|
||||
}
|
||||
dxgi.dropped_frame = false;
|
||||
}
|
||||
|
||||
static void gfx_dxgi_swap_buffers_end(void) {
|
||||
LARGE_INTEGER t0, t1, t2;
|
||||
QueryPerformanceCounter(&t0);
|
||||
QueryPerformanceCounter(&t1);
|
||||
|
||||
if (!dxgi.dropped_frame) {
|
||||
if (dxgi.waitable_object != nullptr) {
|
||||
WaitForSingleObject(dxgi.waitable_object, INFINITE);
|
||||
}
|
||||
// else TODO: maybe sleep until some estimated time the frame will be shown to reduce lag
|
||||
}
|
||||
|
||||
DXGI_FRAME_STATISTICS stats;
|
||||
dxgi.swap_chain->GetFrameStatistics(&stats);
|
||||
|
||||
QueryPerformanceCounter(&t2);
|
||||
|
||||
dxgi.sync_interval_means_frames_to_wait = dxgi.pending_frame_stats.rbegin()->first == stats.PresentCount;
|
||||
|
||||
//printf("done %llu gpu:%d wait:%d freed:%llu frame:%u %u monitor:%u t:%llu\n", (unsigned long long)(t0.QuadPart - dxgi.qpc_init), (int)(t1.QuadPart - t0.QuadPart), (int)(t2.QuadPart - t0.QuadPart), (unsigned long long)(t2.QuadPart - dxgi.qpc_init), dxgi.pending_frame_stats.rbegin()->first, stats.PresentCount, stats.SyncRefreshCount, (unsigned long long)(stats.SyncQPCTime.QuadPart - dxgi.qpc_init));
|
||||
}
|
||||
|
||||
static double gfx_dxgi_get_time(void) {
|
||||
LARGE_INTEGER t;
|
||||
QueryPerformanceCounter(&t);
|
||||
return (double)(t.QuadPart - dxgi.qpc_init) / dxgi.qpc_freq;
|
||||
}
|
||||
|
||||
void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*create_device_fn)(IDXGIAdapter1 *adapter, bool test_only)) {
|
||||
if (dxgi.CreateDXGIFactory2 != nullptr) {
|
||||
ThrowIfFailed(dxgi.CreateDXGIFactory2(debug ? DXGI_CREATE_FACTORY_DEBUG : 0, __uuidof(IDXGIFactory2), &dxgi.factory));
|
||||
} else {
|
||||
ThrowIfFailed(dxgi.CreateDXGIFactory1(__uuidof(IDXGIFactory2), &dxgi.factory));
|
||||
}
|
||||
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
for (UINT i = 0; dxgi.factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; i++) {
|
||||
DXGI_ADAPTER_DESC1 desc;
|
||||
adapter->GetDesc1(&desc);
|
||||
if (desc.Flags & 2/*DXGI_ADAPTER_FLAG_SOFTWARE*/) { // declaration missing in mingw headers
|
||||
continue;
|
||||
}
|
||||
if (create_device_fn(adapter.Get(), true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
create_device_fn(adapter.Get(), false);
|
||||
|
||||
char title[512];
|
||||
wchar_t w_title[512];
|
||||
int len = sprintf(title, "%s (Direct3D %d)", dxgi.game_name.c_str(), d3d_version);
|
||||
mbstowcs(w_title, title, len + 1);
|
||||
SetWindowTextW(dxgi.h_wnd, w_title);
|
||||
}
|
||||
|
||||
ComPtr<IDXGISwapChain1> gfx_dxgi_create_swap_chain(IUnknown *device) {
|
||||
bool win8 = IsWindows8OrGreater(); // DXGI_SCALING_NONE is only supported on Win8 and beyond
|
||||
bool dxgi_13 = dxgi.CreateDXGIFactory2 != nullptr; // DXGI 1.3 introduced waitable object
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
||||
swap_chain_desc.BufferCount = 2;
|
||||
swap_chain_desc.Width = 0;
|
||||
swap_chain_desc.Height = 0;
|
||||
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
swap_chain_desc.Scaling = win8 ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH;
|
||||
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // Apparently this was backported to Win 7 Platform Update
|
||||
swap_chain_desc.Flags = dxgi_13 ? DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT : 0;
|
||||
swap_chain_desc.SampleDesc.Count = 1;
|
||||
|
||||
run_as_dpi_aware([&] () {
|
||||
// When setting size for the buffers, the values that DXGI puts into the desc (that can later be retrieved by GetDesc1)
|
||||
// have been divided by the current scaling factor. By making this call dpi aware, no division will be performed.
|
||||
// The same goes for IDXGISwapChain::ResizeBuffers(), however that function is currently only called from the message handler.
|
||||
ThrowIfFailed(dxgi.factory->CreateSwapChainForHwnd(device, dxgi.h_wnd, &swap_chain_desc, nullptr, nullptr, &dxgi.swap_chain));
|
||||
});
|
||||
ThrowIfFailed(dxgi.factory->MakeWindowAssociation(dxgi.h_wnd, DXGI_MWA_NO_ALT_ENTER));
|
||||
|
||||
ComPtr<IDXGISwapChain2> swap_chain2;
|
||||
if (dxgi.swap_chain->QueryInterface(__uuidof(IDXGISwapChain2), &swap_chain2) == S_OK) {
|
||||
ThrowIfFailed(swap_chain2->SetMaximumFrameLatency(1));
|
||||
dxgi.waitable_object = swap_chain2->GetFrameLatencyWaitableObject();
|
||||
WaitForSingleObject(dxgi.waitable_object, INFINITE);
|
||||
} else {
|
||||
ComPtr<IDXGIDevice1> device1;
|
||||
ThrowIfFailed(device->QueryInterface(IID_PPV_ARGS(&device1)));
|
||||
ThrowIfFailed(device1->SetMaximumFrameLatency(1));
|
||||
}
|
||||
|
||||
ThrowIfFailed(dxgi.swap_chain->GetDesc1(&swap_chain_desc));
|
||||
dxgi.current_width = swap_chain_desc.Width;
|
||||
dxgi.current_height = swap_chain_desc.Height;
|
||||
|
||||
return dxgi.swap_chain;
|
||||
}
|
||||
|
||||
HWND gfx_dxgi_get_h_wnd(void) {
|
||||
return dxgi.h_wnd;
|
||||
}
|
||||
|
||||
void ThrowIfFailed(HRESULT res) {
|
||||
if (FAILED(res)) {
|
||||
fprintf(stderr, "Error: 0x%08X\n", res);
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message) {
|
||||
if (FAILED(res)) {
|
||||
char full_message[256];
|
||||
sprintf(full_message, "%s\n\nHRESULT: 0x%08X", message, res);
|
||||
dxgi.showing_error = true;
|
||||
MessageBox(h_wnd, full_message, "Error", MB_OK | MB_ICONERROR);
|
||||
throw res;
|
||||
}
|
||||
}
|
||||
|
||||
struct GfxWindowManagerAPI gfx_dxgi_api = {
|
||||
gfx_dxgi_init,
|
||||
gfx_dxgi_set_keyboard_callbacks,
|
||||
gfx_dxgi_set_fullscreen_changed_callback,
|
||||
gfx_dxgi_set_fullscreen,
|
||||
gfx_dxgi_main_loop,
|
||||
gfx_dxgi_get_dimensions,
|
||||
gfx_dxgi_handle_events,
|
||||
gfx_dxgi_start_frame,
|
||||
gfx_dxgi_swap_buffers_begin,
|
||||
gfx_dxgi_swap_buffers_end,
|
||||
gfx_dxgi_get_time,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef GFX_DXGI_H
|
||||
#define GFX_DXGI_H
|
||||
|
||||
#include "gfx_rendering_api.h"
|
||||
|
||||
#ifdef DECLARE_GFX_DXGI_FUNCTIONS
|
||||
void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*create_device_fn)(IDXGIAdapter1 *adapter, bool test_only));
|
||||
Microsoft::WRL::ComPtr<IDXGISwapChain1> gfx_dxgi_create_swap_chain(IUnknown *device);
|
||||
HWND gfx_dxgi_get_h_wnd(void);
|
||||
void ThrowIfFailed(HRESULT res);
|
||||
void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message);
|
||||
#endif
|
||||
|
||||
extern struct GfxWindowManagerAPI gfx_dxgi_api;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,620 @@
|
||||
#include "../compat.h"
|
||||
|
||||
#if defined(__linux__) || defined(__BSD__)
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <X11/X.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <GL/glx.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
|
||||
#include "gfx_window_manager_api.h"
|
||||
#include "gfx_screen_config.h"
|
||||
|
||||
#define GFX_API_NAME "GLX - OpenGL"
|
||||
|
||||
#ifdef VERSION_EU
|
||||
#define FRAME_INTERVAL_US_NUMERATOR 40000
|
||||
#define FRAME_INTERVAL_US_DENOMINATOR 1
|
||||
#else
|
||||
#define FRAME_INTERVAL_US_NUMERATOR 100000
|
||||
#define FRAME_INTERVAL_US_DENOMINATOR 3
|
||||
#endif
|
||||
|
||||
const struct {
|
||||
const char *name;
|
||||
int scancode;
|
||||
} keymap_name_to_scancode[] = {
|
||||
{"ESC", 0x01},
|
||||
{"AE01", 0x02 },
|
||||
{"AE02", 0x03 },
|
||||
{"AE03", 0x04 },
|
||||
{"AE04", 0x05 },
|
||||
{"AE05", 0x06 },
|
||||
{"AE06", 0x07 },
|
||||
{"AE07", 0x08 },
|
||||
{"AE08", 0x09 },
|
||||
{"AE09", 0x0a },
|
||||
{"AE10", 0x0b },
|
||||
{"AE11", 0x0c },
|
||||
{"AE12", 0x0d },
|
||||
{"BKSP", 0x0e },
|
||||
{"TAB", 0x0f },
|
||||
{"AD01", 0x10 },
|
||||
{"AD02", 0x11 },
|
||||
{"AD03", 0x12 },
|
||||
{"AD04", 0x13 },
|
||||
{"AD05", 0x14 },
|
||||
{"AD06", 0x15 },
|
||||
{"AD07", 0x16 },
|
||||
{"AD08", 0x17 },
|
||||
{"AD09", 0x18 },
|
||||
{"AD10", 0x19 },
|
||||
{"AD11", 0x1a },
|
||||
{"AD12", 0x1b },
|
||||
{"RTRN", 0x1c },
|
||||
{"LCTL", 0x1d },
|
||||
{"AC01", 0x1e },
|
||||
{"AC02", 0x1f },
|
||||
{"AC03", 0x20 },
|
||||
{"AC04", 0x21 },
|
||||
{"AC05", 0x22 },
|
||||
{"AC06", 0x23 },
|
||||
{"AC07", 0x24 },
|
||||
{"AC08", 0x25 },
|
||||
{"AC09", 0x26 },
|
||||
{"AC10", 0x27 },
|
||||
{"AC11", 0x28 },
|
||||
{"TLDE", 0x29 },
|
||||
{"LFSH", 0x2a },
|
||||
{"BKSL", 0x2b },
|
||||
{"AB01", 0x2c },
|
||||
{"AB02", 0x2d },
|
||||
{"AB03", 0x2e },
|
||||
{"AB04", 0x2f },
|
||||
{"AB05", 0x30 },
|
||||
{"AB06", 0x31 },
|
||||
{"AB07", 0x32 },
|
||||
{"AB08", 0x33 },
|
||||
{"AB09", 0x34 },
|
||||
{"AB10", 0x35 },
|
||||
{"RTSH", 0x36 },
|
||||
{"KPMU", 0x37 },
|
||||
{"LALT", 0x38 },
|
||||
{"SPCE", 0x39 },
|
||||
{"CAPS", 0x3a },
|
||||
{"FK01", 0x3b },
|
||||
{"FK02", 0x3c },
|
||||
{"FK03", 0x3d },
|
||||
{"FK04", 0x3e },
|
||||
{"FK05", 0x3f },
|
||||
{"FK06", 0x40 },
|
||||
{"FK07", 0x41 },
|
||||
{"FK08", 0x42 },
|
||||
{"FK09", 0x43 },
|
||||
{"FK10", 0x44 },
|
||||
{"NMLK", 0x45 },
|
||||
{"SCLK", 0x46 },
|
||||
{"KP7", 0x47 },
|
||||
{"KP8", 0x48 },
|
||||
{"KP9", 0x49 },
|
||||
{"KPSU", 0x4a },
|
||||
{"KP4", 0x4b },
|
||||
{"KP5", 0x4c },
|
||||
{"KP6", 0x4d },
|
||||
{"KPAD", 0x4e },
|
||||
{"KP1", 0x4f },
|
||||
{"KP2", 0x50 },
|
||||
{"KP3", 0x51 },
|
||||
{"KP0", 0x52 },
|
||||
{"KPDL", 0x53 },
|
||||
{"LVL3", 0x54 }, // correct?
|
||||
{"", 0x55 }, // not mapped?
|
||||
{"LSGT", 0x56 },
|
||||
{"FK11", 0x57 },
|
||||
{"FK12", 0x58 },
|
||||
{"AB11", 0x59 },
|
||||
{"KATA", 0 },
|
||||
{"HIRA", 0 },
|
||||
{"HENK", 0 },
|
||||
{"HKTG", 0 },
|
||||
{"MUHE", 0 },
|
||||
{"JPCM", 0 },
|
||||
{"KPEN", 0x11c },
|
||||
{"RCTL", 0x11d },
|
||||
{"KPDV", 0x135 },
|
||||
{"PRSC", 0x54 }, // ?
|
||||
{"RALT", 0x138 },
|
||||
{"LNFD", 0 },
|
||||
{"HOME", 0x147 },
|
||||
{"UP", 0x148 },
|
||||
{"PGUP", 0x149 },
|
||||
{"LEFT", 0x14b },
|
||||
{"RGHT", 0x14d },
|
||||
{"END", 0x14f },
|
||||
{"DOWN", 0x150 },
|
||||
{"PGDN", 0x151 },
|
||||
{"INS", 0x152 },
|
||||
{"DELE", 0x153 },
|
||||
{"PAUS", 0x21d },
|
||||
{"LWIN", 0x15b },
|
||||
{"RWIN", 0x15c },
|
||||
{"COMP", 0x15d },
|
||||
};
|
||||
|
||||
static struct {
|
||||
Display *dpy;
|
||||
Window root;
|
||||
Window win;
|
||||
|
||||
Atom atom_wm_state;
|
||||
Atom atom_wm_state_fullscreen;
|
||||
|
||||
bool is_fullscreen;
|
||||
void (*on_fullscreen_changed)(bool is_now_fullscreen);
|
||||
|
||||
int keymap[256];
|
||||
bool (*on_key_down)(int scancode);
|
||||
bool (*on_key_up)(int scancode);
|
||||
void (*on_all_keys_up)(void);
|
||||
|
||||
PFNGLXGETSYNCVALUESOMLPROC glXGetSyncValuesOML;
|
||||
PFNGLXSWAPBUFFERSMSCOMLPROC glXSwapBuffersMscOML;
|
||||
PFNGLXWAITFORSBCOMLPROC glXWaitForSbcOML;
|
||||
|
||||
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT;
|
||||
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
|
||||
|
||||
PFNGLXGETVIDEOSYNCSGIPROC glXGetVideoSyncSGI;
|
||||
PFNGLXWAITVIDEOSYNCSGIPROC glXWaitVideoSyncSGI;
|
||||
|
||||
bool has_oml_sync_control;
|
||||
uint64_t ust0;
|
||||
int64_t last_msc;
|
||||
uint64_t wanted_ust; // multiplied by FRAME_INTERVAL_US_DENOMINATOR
|
||||
uint64_t vsync_interval;
|
||||
uint64_t last_ust;
|
||||
int64_t target_msc;
|
||||
bool dropped_frame;
|
||||
|
||||
bool has_sgi_video_sync;
|
||||
uint64_t last_sync_counter;
|
||||
int64_t this_msc;
|
||||
int64_t this_ust;
|
||||
} glx;
|
||||
|
||||
static int64_t get_time(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
|
||||
}
|
||||
|
||||
static int64_t adjust_sync_counter(uint32_t counter) {
|
||||
uint32_t hi = glx.last_sync_counter >> 32;
|
||||
uint32_t lo = (uint32_t)glx.last_sync_counter;
|
||||
if (lo >= 0x80000000U && counter < 0x80000000U) {
|
||||
// Wrapped
|
||||
++hi;
|
||||
}
|
||||
glx.last_sync_counter = ((uint64_t)hi << 32) | counter;
|
||||
return glx.last_sync_counter;
|
||||
}
|
||||
|
||||
static int64_t glXWaitVideoSyncSGI_wrapper(void) {
|
||||
unsigned int counter = 0;
|
||||
glx.glXWaitVideoSyncSGI(1, 0, &counter);
|
||||
return adjust_sync_counter(counter);
|
||||
}
|
||||
|
||||
static int64_t glXGetVideoSyncSGI_wrapper(void) {
|
||||
unsigned int counter = 0;
|
||||
glx.glXGetVideoSyncSGI(&counter);
|
||||
return adjust_sync_counter(counter);
|
||||
}
|
||||
|
||||
static void init_keymap(void) {
|
||||
XkbDescPtr desc = XkbGetMap(glx.dpy, 0, XkbUseCoreKbd);
|
||||
XkbGetNames(glx.dpy, XkbKeyNamesMask, desc);
|
||||
|
||||
for (int i = desc->min_key_code; i <= desc->max_key_code && i < 256; i++) {
|
||||
char name[XkbKeyNameLength + 1];
|
||||
memcpy(name, desc->names->keys[i].name, XkbKeyNameLength);
|
||||
name[XkbKeyNameLength] = '\0';
|
||||
for (size_t j = 0; j < sizeof(keymap_name_to_scancode) / sizeof(keymap_name_to_scancode[0]); j++) {
|
||||
if (strcmp(keymap_name_to_scancode[j].name, name) == 0) {
|
||||
glx.keymap[i] = keymap_name_to_scancode[j].scancode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XkbFreeNames(desc, XkbKeyNamesMask, True);
|
||||
XkbFreeKeyboard(desc, 0, True);
|
||||
}
|
||||
|
||||
static void gfx_glx_hide_mouse(bool hide) {
|
||||
// Removes distracting mouse cursor during fullscreen play
|
||||
if (hide) {
|
||||
Cursor hideCursor;
|
||||
Pixmap bitmapNoData;
|
||||
XColor black;
|
||||
static char noData[] = { 0,0,0,0,0,0,0,0 };
|
||||
black.red = black.green = black.blue = 0;
|
||||
|
||||
bitmapNoData = XCreateBitmapFromData(glx.dpy, glx.win, noData, 8, 8);
|
||||
hideCursor = XCreatePixmapCursor(glx.dpy, bitmapNoData, bitmapNoData,
|
||||
&black, &black, 0, 0);
|
||||
XDefineCursor(glx.dpy, glx.win, hideCursor);
|
||||
XSync(glx.dpy, False);
|
||||
XFreeCursor(glx.dpy, hideCursor);
|
||||
XFreePixmap(glx.dpy, bitmapNoData);
|
||||
} else {
|
||||
XUndefineCursor(glx.dpy, glx.win);
|
||||
XSync(glx.dpy, False);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gfx_glx_set_fullscreen_state(bool on, bool call_callback) {
|
||||
if (glx.is_fullscreen == on) {
|
||||
return;
|
||||
}
|
||||
glx.is_fullscreen = on;
|
||||
|
||||
XEvent xev;
|
||||
xev.xany.type = ClientMessage;
|
||||
xev.xclient.message_type = glx.atom_wm_state;
|
||||
xev.xclient.format = 32;
|
||||
xev.xclient.window = glx.win;
|
||||
xev.xclient.data.l[0] = on;
|
||||
xev.xclient.data.l[1] = glx.atom_wm_state_fullscreen;
|
||||
xev.xclient.data.l[2] = 0;
|
||||
xev.xclient.data.l[3] = 0;
|
||||
XSendEvent(glx.dpy, glx.root, 0, SubstructureNotifyMask | SubstructureRedirectMask, &xev);
|
||||
gfx_glx_hide_mouse(on);
|
||||
|
||||
if (glx.on_fullscreen_changed != NULL && call_callback) {
|
||||
glx.on_fullscreen_changed(on);
|
||||
}
|
||||
}
|
||||
|
||||
static bool gfx_glx_check_extension(const char *extensions, const char *extension) {
|
||||
size_t len = strlen(extension);
|
||||
const char *pos = extensions;
|
||||
while ((pos = strstr(pos, extension)) != NULL) {
|
||||
if ((pos[len] == ' ' || pos[len] == '\0') && (pos == extensions || pos[-1] == ' ')) {
|
||||
return true;
|
||||
}
|
||||
if (pos[len] == '\0') {
|
||||
break;
|
||||
}
|
||||
pos += len + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void gfx_glx_init(const char *game_name, bool start_in_fullscreen) {
|
||||
// On NVIDIA proprietary driver, make the driver queue up to two frames on glXSwapBuffers,
|
||||
// which means that glXSwapBuffers should be non-blocking,
|
||||
// if we are sure to wait at least one vsync interval between calls.
|
||||
setenv("__GL_MaxFramesAllowed", "2", true);
|
||||
|
||||
glx.dpy = XOpenDisplay(NULL);
|
||||
if (glx.dpy == NULL) {
|
||||
fprintf(stderr, "Cannot connect to X server\n");
|
||||
exit(1);
|
||||
}
|
||||
int screen = DefaultScreen(glx.dpy);
|
||||
glx.root = RootWindow(glx.dpy, screen);
|
||||
|
||||
GLint att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None };
|
||||
XVisualInfo *vi = glXChooseVisual(glx.dpy, 0, att);
|
||||
if (vi == NULL) {
|
||||
fprintf(stderr, "No appropriate GLX visual found\n");
|
||||
exit(1);
|
||||
}
|
||||
Colormap cmap = XCreateColormap(glx.dpy, glx.root, vi->visual, AllocNone);
|
||||
XSetWindowAttributes swa;
|
||||
swa.colormap = cmap;
|
||||
swa.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | FocusChangeMask;
|
||||
glx.win = XCreateWindow(glx.dpy, glx.root, 0, 0, DESIRED_SCREEN_WIDTH, DESIRED_SCREEN_HEIGHT, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa);
|
||||
|
||||
glx.atom_wm_state = XInternAtom(glx.dpy, "_NET_WM_STATE", False);
|
||||
glx.atom_wm_state_fullscreen = XInternAtom(glx.dpy, "_NET_WM_STATE_FULLSCREEN", False);
|
||||
XMapWindow(glx.dpy, glx.win);
|
||||
|
||||
if (start_in_fullscreen) {
|
||||
gfx_glx_set_fullscreen_state(true, false);
|
||||
}
|
||||
|
||||
char title[512];
|
||||
int len = sprintf(title, "%s (%s)", game_name, GFX_API_NAME);
|
||||
|
||||
XStoreName(glx.dpy, glx.win, title);
|
||||
GLXContext glc = glXCreateContext(glx.dpy, vi, NULL, GL_TRUE);
|
||||
glXMakeCurrent(glx.dpy, glx.win, glc);
|
||||
|
||||
init_keymap();
|
||||
|
||||
const char *extensions = glXQueryExtensionsString(glx.dpy, screen);
|
||||
|
||||
if (gfx_glx_check_extension(extensions, "GLX_OML_sync_control")) {
|
||||
glx.glXGetSyncValuesOML = (PFNGLXGETSYNCVALUESOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXGetSyncValuesOML");
|
||||
glx.glXSwapBuffersMscOML = (PFNGLXSWAPBUFFERSMSCOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapBuffersMscOML");
|
||||
glx.glXWaitForSbcOML = (PFNGLXWAITFORSBCOMLPROC)glXGetProcAddressARB((const GLubyte *)"glXWaitForSbcOML");
|
||||
}
|
||||
if (gfx_glx_check_extension(extensions, "GLX_EXT_swap_control")) {
|
||||
glx.glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT");
|
||||
}
|
||||
if (gfx_glx_check_extension(extensions, "GLX_SGI_swap_control")) {
|
||||
glx.glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI");
|
||||
}
|
||||
if (gfx_glx_check_extension(extensions, "GLX_SGI_video_sync")) {
|
||||
glx.glXGetVideoSyncSGI = (PFNGLXGETVIDEOSYNCSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXGetVideoSyncSGI");
|
||||
glx.glXWaitVideoSyncSGI = (PFNGLXWAITVIDEOSYNCSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXWaitVideoSyncSGI");
|
||||
}
|
||||
|
||||
int64_t ust, msc, sbc;
|
||||
if (glx.glXGetSyncValuesOML != NULL && glx.glXGetSyncValuesOML(glx.dpy, glx.win, &ust, &msc, &sbc)) {
|
||||
glx.has_oml_sync_control = true;
|
||||
glx.ust0 = (uint64_t)ust;
|
||||
} else {
|
||||
glx.ust0 = get_time();
|
||||
if (glx.glXSwapIntervalEXT != NULL) {
|
||||
glx.glXSwapIntervalEXT(glx.dpy, glx.win, 1);
|
||||
} else if (glx.glXSwapIntervalSGI != NULL) {
|
||||
glx.glXSwapIntervalSGI(1);
|
||||
}
|
||||
if (glx.glXGetVideoSyncSGI != NULL) {
|
||||
// Try if it really works
|
||||
unsigned int count;
|
||||
if (glx.glXGetVideoSyncSGI(&count) == 0) {
|
||||
glx.last_sync_counter = count;
|
||||
glx.has_sgi_video_sync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
glx.vsync_interval = 16666;
|
||||
}
|
||||
|
||||
static void gfx_glx_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
|
||||
glx.on_fullscreen_changed = on_fullscreen_changed;
|
||||
}
|
||||
|
||||
static void gfx_glx_set_fullscreen(bool enable) {
|
||||
gfx_glx_set_fullscreen_state(enable, true);
|
||||
}
|
||||
|
||||
static void gfx_glx_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) {
|
||||
glx.on_key_down = on_key_down;
|
||||
glx.on_key_up = on_key_up;
|
||||
glx.on_all_keys_up = on_all_keys_up;
|
||||
}
|
||||
|
||||
static void gfx_glx_main_loop(void (*run_one_game_iter)(void)) {
|
||||
while (1) {
|
||||
run_one_game_iter();
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_glx_get_dimensions(uint32_t *width, uint32_t *height) {
|
||||
XWindowAttributes attributes;
|
||||
XGetWindowAttributes(glx.dpy, glx.win, &attributes);
|
||||
*width = attributes.width;
|
||||
*height = attributes.height;
|
||||
}
|
||||
|
||||
static void gfx_glx_handle_events(void) {
|
||||
Atom wm_delete_window = XInternAtom(glx.dpy, "WM_DELETE_WINDOW", 0);
|
||||
XSetWMProtocols(glx.dpy, glx.win, & wm_delete_window, 1);
|
||||
while (XPending(glx.dpy)) {
|
||||
XEvent xev;
|
||||
XNextEvent(glx.dpy, &xev);
|
||||
if (xev.type == FocusOut) {
|
||||
if (glx.on_all_keys_up != NULL) {
|
||||
glx.on_all_keys_up();
|
||||
}
|
||||
}
|
||||
if (xev.type == KeyPress || xev.type == KeyRelease) {
|
||||
if (xev.xkey.keycode < 256) {
|
||||
int scancode = glx.keymap[xev.xkey.keycode];
|
||||
if (scancode != 0) {
|
||||
if (xev.type == KeyPress) {
|
||||
if (scancode == 0x44) { // F10
|
||||
gfx_glx_set_fullscreen_state(!glx.is_fullscreen, true);
|
||||
}
|
||||
if (glx.on_key_down != NULL) {
|
||||
glx.on_key_down(scancode);
|
||||
}
|
||||
} else {
|
||||
if (glx.on_key_up != NULL) {
|
||||
glx.on_key_up(scancode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xev.type == ClientMessage) {
|
||||
if (xev.xclient.data.l[0] == wm_delete_window) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool gfx_glx_start_frame(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gfx_glx_swap_buffers_begin(void) {
|
||||
glx.wanted_ust += FRAME_INTERVAL_US_NUMERATOR; // advance 1/30 seconds on JP/US or 1/25 seconds on EU
|
||||
|
||||
if (!glx.has_oml_sync_control && !glx.has_sgi_video_sync) {
|
||||
glFlush();
|
||||
|
||||
uint64_t target = glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR;
|
||||
uint64_t now;
|
||||
while (target > (now = (uint64_t)get_time() - glx.ust0)) {
|
||||
struct timespec ts = {(target - now) / 1000000, ((target - now) % 1000000) * 1000};
|
||||
if (nanosleep(&ts, NULL) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target + 2 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR < now) {
|
||||
if (target + 32 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR >= now) {
|
||||
printf("Dropping frame\n");
|
||||
glx.dropped_frame = true;
|
||||
return;
|
||||
} else {
|
||||
// Reset timer since we are way out of sync
|
||||
glx.wanted_ust = now * FRAME_INTERVAL_US_DENOMINATOR;
|
||||
}
|
||||
}
|
||||
glXSwapBuffers(glx.dpy, glx.win);
|
||||
glx.dropped_frame = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
double vsyncs_to_wait = (int64_t)(glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR - glx.last_ust) / (double)glx.vsync_interval;
|
||||
if (vsyncs_to_wait <= 0) {
|
||||
printf("Dropping frame\n");
|
||||
// Drop frame
|
||||
glx.dropped_frame = true;
|
||||
return;
|
||||
}
|
||||
if (floor(vsyncs_to_wait) != vsyncs_to_wait) {
|
||||
uint64_t left_ust = glx.last_ust + floor(vsyncs_to_wait) * glx.vsync_interval;
|
||||
uint64_t right_ust = glx.last_ust + ceil(vsyncs_to_wait) * glx.vsync_interval;
|
||||
uint64_t adjusted_wanted_ust = glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR + (glx.last_ust + FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR > glx.wanted_ust / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000);
|
||||
int64_t diff_left = adjusted_wanted_ust - left_ust;
|
||||
int64_t diff_right = right_ust - adjusted_wanted_ust;
|
||||
if (diff_left < 0) {
|
||||
diff_left = -diff_left;
|
||||
}
|
||||
if (diff_right < 0) {
|
||||
diff_right = -diff_right;
|
||||
}
|
||||
if (diff_left < diff_right) {
|
||||
vsyncs_to_wait = floor(vsyncs_to_wait);
|
||||
} else {
|
||||
vsyncs_to_wait = ceil(vsyncs_to_wait);
|
||||
}
|
||||
if (vsyncs_to_wait <= -4) {
|
||||
printf("vsyncs_to_wait became -4 or less so dropping frame\n");
|
||||
glx.dropped_frame = true;
|
||||
return;
|
||||
} else if (vsyncs_to_wait < 1) {
|
||||
vsyncs_to_wait = 1;
|
||||
}
|
||||
}
|
||||
glx.dropped_frame = false;
|
||||
//printf("Vsyncs to wait: %d, diff: %d\n", (int)vsyncs_to_wait, (int)(glx.last_ust + (int64_t)vsyncs_to_wait * glx.vsync_interval - glx.wanted_ust / 3));
|
||||
if (vsyncs_to_wait > 30) {
|
||||
// Unreasonable, so change to 2
|
||||
vsyncs_to_wait = 2;
|
||||
}
|
||||
glx.target_msc = glx.last_msc + vsyncs_to_wait;
|
||||
|
||||
if (glx.has_oml_sync_control) {
|
||||
glx.glXSwapBuffersMscOML(glx.dpy, glx.win, glx.target_msc, 0, 0);
|
||||
} else if (glx.has_sgi_video_sync) {
|
||||
glFlush(); // Try to submit pending work. Don't use glFinish since that busy loops on NVIDIA proprietary driver.
|
||||
|
||||
//uint64_t counter0;
|
||||
uint64_t counter1, counter2;
|
||||
|
||||
//uint64_t before_wait = get_time();
|
||||
|
||||
counter1 = glXGetVideoSyncSGI_wrapper();
|
||||
//counter0 = counter1;
|
||||
//int waits = 0;
|
||||
while (counter1 < (uint64_t)glx.target_msc - 1) {
|
||||
counter1 = glXWaitVideoSyncSGI_wrapper();
|
||||
//++waits;
|
||||
}
|
||||
|
||||
//uint64_t before = get_time();
|
||||
glXSwapBuffers(glx.dpy, glx.win);
|
||||
|
||||
|
||||
counter2 = glXGetVideoSyncSGI_wrapper();
|
||||
while (counter2 < (uint64_t)glx.target_msc) {
|
||||
counter2 = glXWaitVideoSyncSGI_wrapper();
|
||||
}
|
||||
uint64_t after = get_time();
|
||||
|
||||
//printf("%.3f %.3f %.3f\t%.3f\t%u %d %.2f %u %d\n", before_wait * 0.000060, before * 0.000060, after * 0.000060, (after - before) * 0.000060, counter0, counter2 - counter0, vsyncs_to_wait, (unsigned int)glx.target_msc, waits);
|
||||
glx.this_msc = counter2;
|
||||
glx.this_ust = after;
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_glx_swap_buffers_end(void) {
|
||||
if (glx.dropped_frame || (!glx.has_oml_sync_control && !glx.has_sgi_video_sync)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t ust, msc, sbc;
|
||||
if (glx.has_oml_sync_control) {
|
||||
if (!glx.glXWaitForSbcOML(glx.dpy, glx.win, 0, &ust, &msc, &sbc)) {
|
||||
// X connection broke or something?
|
||||
glx.last_ust += (glx.target_msc - glx.last_msc) * glx.vsync_interval;
|
||||
glx.last_msc = glx.target_msc;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ust = glx.this_ust;
|
||||
msc = glx.this_msc;
|
||||
}
|
||||
uint64_t this_ust = ust - glx.ust0;
|
||||
uint64_t vsyncs_passed = msc - glx.last_msc;
|
||||
bool bad_vsync_interval = false;
|
||||
if (glx.last_ust != 0 && vsyncs_passed != 0) {
|
||||
uint64_t new_vsync_interval = (this_ust - glx.last_ust) / vsyncs_passed;
|
||||
if (new_vsync_interval <= 500000) {
|
||||
// Should be less than 0.5 seconds to be trusted
|
||||
glx.vsync_interval = new_vsync_interval;
|
||||
} else {
|
||||
bad_vsync_interval = true;
|
||||
}
|
||||
//printf("glx.vsync_interval: %d\n", (int)glx.vsync_interval);
|
||||
}
|
||||
glx.last_ust = this_ust;
|
||||
glx.last_msc = msc;
|
||||
if (msc != glx.target_msc) {
|
||||
printf("Frame too late by %d vsyncs\n", (int)(msc - glx.target_msc));
|
||||
}
|
||||
if (msc - glx.target_msc >= 8 || bad_vsync_interval) {
|
||||
// Frame arrived way too late, so reset timer from here
|
||||
printf("Reseting timer\n");
|
||||
glx.wanted_ust = this_ust * FRAME_INTERVAL_US_DENOMINATOR;
|
||||
}
|
||||
}
|
||||
|
||||
static double gfx_glx_get_time(void) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
struct GfxWindowManagerAPI gfx_glx = {
|
||||
gfx_glx_init,
|
||||
gfx_glx_set_keyboard_callbacks,
|
||||
gfx_glx_set_fullscreen_changed_callback,
|
||||
gfx_glx_set_fullscreen,
|
||||
gfx_glx_main_loop,
|
||||
gfx_glx_get_dimensions,
|
||||
gfx_glx_handle_events,
|
||||
gfx_glx_start_frame,
|
||||
gfx_glx_swap_buffers_begin,
|
||||
gfx_glx_swap_buffers_end,
|
||||
gfx_glx_get_time
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef GFX_GLX_H
|
||||
#define GFX_GLX_H
|
||||
|
||||
#include "gfx_window_manager_api.h"
|
||||
|
||||
extern struct GfxWindowManagerAPI gfx_glx;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,531 @@
|
||||
#ifdef ENABLE_OPENGL
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef _LANGUAGE_C
|
||||
#define _LANGUAGE_C
|
||||
#endif
|
||||
#include <PR/gbi.h>
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#define FOR_WINDOWS 1
|
||||
#else
|
||||
#define FOR_WINDOWS 0
|
||||
#endif
|
||||
|
||||
#if FOR_WINDOWS
|
||||
#include <GL/glew.h>
|
||||
#include "SDL.h"
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include "SDL_opengl.h"
|
||||
#else
|
||||
#include <SDL2/SDL.h>
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include <SDL2/SDL_opengles2.h>
|
||||
#endif
|
||||
|
||||
#include "gfx_cc.h"
|
||||
#include "gfx_rendering_api.h"
|
||||
|
||||
struct ShaderProgram {
|
||||
uint32_t shader_id;
|
||||
GLuint opengl_program_id;
|
||||
uint8_t num_inputs;
|
||||
bool used_textures[2];
|
||||
uint8_t num_floats;
|
||||
GLint attrib_locations[7];
|
||||
uint8_t attrib_sizes[7];
|
||||
uint8_t num_attribs;
|
||||
bool used_noise;
|
||||
GLint frame_count_location;
|
||||
GLint window_height_location;
|
||||
};
|
||||
|
||||
static struct ShaderProgram shader_program_pool[64];
|
||||
static uint8_t shader_program_pool_size;
|
||||
static GLuint opengl_vbo;
|
||||
|
||||
static uint32_t frame_count;
|
||||
static uint32_t current_height;
|
||||
|
||||
static bool gfx_opengl_z_is_from_0_to_1(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void gfx_opengl_vertex_array_set_attribs(struct ShaderProgram *prg) {
|
||||
size_t num_floats = prg->num_floats;
|
||||
size_t pos = 0;
|
||||
|
||||
for (int i = 0; i < prg->num_attribs; i++) {
|
||||
glEnableVertexAttribArray(prg->attrib_locations[i]);
|
||||
glVertexAttribPointer(prg->attrib_locations[i], prg->attrib_sizes[i], GL_FLOAT, GL_FALSE, num_floats * sizeof(float), (void *) (pos * sizeof(float)));
|
||||
pos += prg->attrib_sizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_uniforms(struct ShaderProgram *prg) {
|
||||
if (prg->used_noise) {
|
||||
glUniform1i(prg->frame_count_location, frame_count);
|
||||
glUniform1i(prg->window_height_location, current_height);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_opengl_unload_shader(struct ShaderProgram *old_prg) {
|
||||
if (old_prg != NULL) {
|
||||
for (int i = 0; i < old_prg->num_attribs; i++) {
|
||||
glDisableVertexAttribArray(old_prg->attrib_locations[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_opengl_load_shader(struct ShaderProgram *new_prg) {
|
||||
glUseProgram(new_prg->opengl_program_id);
|
||||
gfx_opengl_vertex_array_set_attribs(new_prg);
|
||||
gfx_opengl_set_uniforms(new_prg);
|
||||
}
|
||||
|
||||
static void append_str(char *buf, size_t *len, const char *str) {
|
||||
while (*str != '\0') buf[(*len)++] = *str++;
|
||||
}
|
||||
|
||||
static void append_line(char *buf, size_t *len, const char *str) {
|
||||
while (*str != '\0') buf[(*len)++] = *str++;
|
||||
buf[(*len)++] = '\n';
|
||||
}
|
||||
|
||||
static const char *shader_item_to_str(uint32_t item, bool with_alpha, bool only_alpha, bool inputs_have_alpha, bool hint_single_element) {
|
||||
if (!only_alpha) {
|
||||
switch (item) {
|
||||
case SHADER_0:
|
||||
return with_alpha ? "vec4(0.0, 0.0, 0.0, 0.0)" : "vec3(0.0, 0.0, 0.0)";
|
||||
case SHADER_INPUT_1:
|
||||
return with_alpha || !inputs_have_alpha ? "vInput1" : "vInput1.rgb";
|
||||
case SHADER_INPUT_2:
|
||||
return with_alpha || !inputs_have_alpha ? "vInput2" : "vInput2.rgb";
|
||||
case SHADER_INPUT_3:
|
||||
return with_alpha || !inputs_have_alpha ? "vInput3" : "vInput3.rgb";
|
||||
case SHADER_INPUT_4:
|
||||
return with_alpha || !inputs_have_alpha ? "vInput4" : "vInput4.rgb";
|
||||
case SHADER_TEXEL0:
|
||||
return with_alpha ? "texVal0" : "texVal0.rgb";
|
||||
case SHADER_TEXEL0A:
|
||||
return hint_single_element ? "texVal0.a" :
|
||||
(with_alpha ? "vec4(texVal0.a, texVal0.a, texVal0.a, texVal0.a)" : "vec3(texVal0.a, texVal0.a, texVal0.a)");
|
||||
case SHADER_TEXEL1:
|
||||
return with_alpha ? "texVal1" : "texVal1.rgb";
|
||||
}
|
||||
} else {
|
||||
switch (item) {
|
||||
case SHADER_0:
|
||||
return "0.0";
|
||||
case SHADER_INPUT_1:
|
||||
return "vInput1.a";
|
||||
case SHADER_INPUT_2:
|
||||
return "vInput2.a";
|
||||
case SHADER_INPUT_3:
|
||||
return "vInput3.a";
|
||||
case SHADER_INPUT_4:
|
||||
return "vInput4.a";
|
||||
case SHADER_TEXEL0:
|
||||
return "texVal0.a";
|
||||
case SHADER_TEXEL0A:
|
||||
return "texVal0.a";
|
||||
case SHADER_TEXEL1:
|
||||
return "texVal1.a";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void append_formula(char *buf, size_t *len, uint8_t c[2][4], bool do_single, bool do_multiply, bool do_mix, bool with_alpha, bool only_alpha, bool opt_alpha) {
|
||||
if (do_single) {
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false));
|
||||
} else if (do_multiply) {
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, " * ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
|
||||
} else if (do_mix) {
|
||||
append_str(buf, len, "mix(");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, ", ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, ", ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
|
||||
append_str(buf, len, ")");
|
||||
} else {
|
||||
append_str(buf, len, "(");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][0], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, " - ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][1], with_alpha, only_alpha, opt_alpha, false));
|
||||
append_str(buf, len, ") * ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][2], with_alpha, only_alpha, opt_alpha, true));
|
||||
append_str(buf, len, " + ");
|
||||
append_str(buf, len, shader_item_to_str(c[only_alpha][3], with_alpha, only_alpha, opt_alpha, false));
|
||||
}
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(uint32_t shader_id) {
|
||||
struct CCFeatures cc_features;
|
||||
gfx_cc_get_features(shader_id, &cc_features);
|
||||
|
||||
char vs_buf[1024];
|
||||
char fs_buf[1024];
|
||||
size_t vs_len = 0;
|
||||
size_t fs_len = 0;
|
||||
size_t num_floats = 4;
|
||||
|
||||
// Vertex shader
|
||||
append_line(vs_buf, &vs_len, "#version 110");
|
||||
append_line(vs_buf, &vs_len, "attribute vec4 aVtxPos;");
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
append_line(vs_buf, &vs_len, "attribute vec2 aTexCoord;");
|
||||
append_line(vs_buf, &vs_len, "varying vec2 vTexCoord;");
|
||||
num_floats += 2;
|
||||
}
|
||||
if (cc_features.opt_fog) {
|
||||
append_line(vs_buf, &vs_len, "attribute vec4 aFog;");
|
||||
append_line(vs_buf, &vs_len, "varying vec4 vFog;");
|
||||
num_floats += 4;
|
||||
}
|
||||
for (int i = 0; i < cc_features.num_inputs; i++) {
|
||||
vs_len += sprintf(vs_buf + vs_len, "attribute vec%d aInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
|
||||
vs_len += sprintf(vs_buf + vs_len, "varying vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
|
||||
num_floats += cc_features.opt_alpha ? 4 : 3;
|
||||
}
|
||||
append_line(vs_buf, &vs_len, "void main() {");
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
append_line(vs_buf, &vs_len, "vTexCoord = aTexCoord;");
|
||||
}
|
||||
if (cc_features.opt_fog) {
|
||||
append_line(vs_buf, &vs_len, "vFog = aFog;");
|
||||
}
|
||||
for (int i = 0; i < cc_features.num_inputs; i++) {
|
||||
vs_len += sprintf(vs_buf + vs_len, "vInput%d = aInput%d;\n", i + 1, i + 1);
|
||||
}
|
||||
append_line(vs_buf, &vs_len, "gl_Position = aVtxPos;");
|
||||
append_line(vs_buf, &vs_len, "}");
|
||||
|
||||
// Fragment shader
|
||||
append_line(fs_buf, &fs_len, "#version 110");
|
||||
//append_line(fs_buf, &fs_len, "precision mediump float;");
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
append_line(fs_buf, &fs_len, "varying vec2 vTexCoord;");
|
||||
}
|
||||
if (cc_features.opt_fog) {
|
||||
append_line(fs_buf, &fs_len, "varying vec4 vFog;");
|
||||
}
|
||||
for (int i = 0; i < cc_features.num_inputs; i++) {
|
||||
fs_len += sprintf(fs_buf + fs_len, "varying vec%d vInput%d;\n", cc_features.opt_alpha ? 4 : 3, i + 1);
|
||||
}
|
||||
if (cc_features.used_textures[0]) {
|
||||
append_line(fs_buf, &fs_len, "uniform sampler2D uTex0;");
|
||||
}
|
||||
if (cc_features.used_textures[1]) {
|
||||
append_line(fs_buf, &fs_len, "uniform sampler2D uTex1;");
|
||||
}
|
||||
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
append_line(fs_buf, &fs_len, "uniform int frame_count;");
|
||||
append_line(fs_buf, &fs_len, "uniform int window_height;");
|
||||
|
||||
append_line(fs_buf, &fs_len, "float random(in vec3 value) {");
|
||||
append_line(fs_buf, &fs_len, " float random = dot(sin(value), vec3(12.9898, 78.233, 37.719));");
|
||||
append_line(fs_buf, &fs_len, " return fract(sin(random) * 143758.5453);");
|
||||
append_line(fs_buf, &fs_len, "}");
|
||||
}
|
||||
|
||||
append_line(fs_buf, &fs_len, "void main() {");
|
||||
|
||||
if (cc_features.used_textures[0]) {
|
||||
append_line(fs_buf, &fs_len, "vec4 texVal0 = texture2D(uTex0, vTexCoord);");
|
||||
}
|
||||
if (cc_features.used_textures[1]) {
|
||||
append_line(fs_buf, &fs_len, "vec4 texVal1 = texture2D(uTex1, vTexCoord);");
|
||||
}
|
||||
|
||||
append_str(fs_buf, &fs_len, cc_features.opt_alpha ? "vec4 texel = " : "vec3 texel = ");
|
||||
if (!cc_features.color_alpha_same && cc_features.opt_alpha) {
|
||||
append_str(fs_buf, &fs_len, "vec4(");
|
||||
append_formula(fs_buf, &fs_len, cc_features.c, cc_features.do_single[0], cc_features.do_multiply[0], cc_features.do_mix[0], false, false, true);
|
||||
append_str(fs_buf, &fs_len, ", ");
|
||||
append_formula(fs_buf, &fs_len, cc_features.c, cc_features.do_single[1], cc_features.do_multiply[1], cc_features.do_mix[1], true, true, true);
|
||||
append_str(fs_buf, &fs_len, ")");
|
||||
} else {
|
||||
append_formula(fs_buf, &fs_len, cc_features.c, cc_features.do_single[0], cc_features.do_multiply[0], cc_features.do_mix[0], cc_features.opt_alpha, false, cc_features.opt_alpha);
|
||||
}
|
||||
append_line(fs_buf, &fs_len, ";");
|
||||
|
||||
if (cc_features.opt_texture_edge && cc_features.opt_alpha) {
|
||||
append_line(fs_buf, &fs_len, "if (texel.a > 0.3) texel.a = 1.0; else discard;");
|
||||
}
|
||||
// TODO discard if alpha is 0?
|
||||
if (cc_features.opt_fog) {
|
||||
if (cc_features.opt_alpha) {
|
||||
append_line(fs_buf, &fs_len, "texel = vec4(mix(texel.rgb, vFog.rgb, vFog.a), texel.a);");
|
||||
} else {
|
||||
append_line(fs_buf, &fs_len, "texel = mix(texel, vFog.rgb, vFog.a);");
|
||||
}
|
||||
}
|
||||
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
append_line(fs_buf, &fs_len, "texel.a *= floor(random(vec3(floor(gl_FragCoord.xy * (240.0 / float(window_height))), float(frame_count))) + 0.5);");
|
||||
}
|
||||
|
||||
if (cc_features.opt_alpha) {
|
||||
append_line(fs_buf, &fs_len, "gl_FragColor = texel;");
|
||||
} else {
|
||||
append_line(fs_buf, &fs_len, "gl_FragColor = vec4(texel, 1.0);");
|
||||
}
|
||||
append_line(fs_buf, &fs_len, "}");
|
||||
|
||||
vs_buf[vs_len] = '\0';
|
||||
fs_buf[fs_len] = '\0';
|
||||
|
||||
/*puts("Vertex shader:");
|
||||
puts(vs_buf);
|
||||
puts("Fragment shader:");
|
||||
puts(fs_buf);
|
||||
puts("End");*/
|
||||
|
||||
const GLchar *sources[2] = { vs_buf, fs_buf };
|
||||
const GLint lengths[2] = { vs_len, fs_len };
|
||||
GLint success;
|
||||
|
||||
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vertex_shader, 1, &sources[0], &lengths[0]);
|
||||
glCompileShader(vertex_shader);
|
||||
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
GLint max_length = 0;
|
||||
glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &max_length);
|
||||
char error_log[1024];
|
||||
fprintf(stderr, "Vertex shader compilation failed\n");
|
||||
glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]);
|
||||
fprintf(stderr, "%s\n", &error_log[0]);
|
||||
abort();
|
||||
}
|
||||
|
||||
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fragment_shader, 1, &sources[1], &lengths[1]);
|
||||
glCompileShader(fragment_shader);
|
||||
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
GLint max_length = 0;
|
||||
glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &max_length);
|
||||
char error_log[1024];
|
||||
fprintf(stderr, "Fragment shader compilation failed\n");
|
||||
glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]);
|
||||
fprintf(stderr, "%s\n", &error_log[0]);
|
||||
abort();
|
||||
}
|
||||
|
||||
GLuint shader_program = glCreateProgram();
|
||||
glAttachShader(shader_program, vertex_shader);
|
||||
glAttachShader(shader_program, fragment_shader);
|
||||
glLinkProgram(shader_program);
|
||||
|
||||
size_t cnt = 0;
|
||||
|
||||
struct ShaderProgram *prg = &shader_program_pool[shader_program_pool_size++];
|
||||
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aVtxPos");
|
||||
prg->attrib_sizes[cnt] = 4;
|
||||
++cnt;
|
||||
|
||||
if (cc_features.used_textures[0] || cc_features.used_textures[1]) {
|
||||
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aTexCoord");
|
||||
prg->attrib_sizes[cnt] = 2;
|
||||
++cnt;
|
||||
}
|
||||
|
||||
if (cc_features.opt_fog) {
|
||||
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, "aFog");
|
||||
prg->attrib_sizes[cnt] = 4;
|
||||
++cnt;
|
||||
}
|
||||
|
||||
for (int i = 0; i < cc_features.num_inputs; i++) {
|
||||
char name[16];
|
||||
sprintf(name, "aInput%d", i + 1);
|
||||
prg->attrib_locations[cnt] = glGetAttribLocation(shader_program, name);
|
||||
prg->attrib_sizes[cnt] = cc_features.opt_alpha ? 4 : 3;
|
||||
++cnt;
|
||||
}
|
||||
|
||||
prg->shader_id = shader_id;
|
||||
prg->opengl_program_id = shader_program;
|
||||
prg->num_inputs = cc_features.num_inputs;
|
||||
prg->used_textures[0] = cc_features.used_textures[0];
|
||||
prg->used_textures[1] = cc_features.used_textures[1];
|
||||
prg->num_floats = num_floats;
|
||||
prg->num_attribs = cnt;
|
||||
|
||||
gfx_opengl_load_shader(prg);
|
||||
|
||||
if (cc_features.used_textures[0]) {
|
||||
GLint sampler_location = glGetUniformLocation(shader_program, "uTex0");
|
||||
glUniform1i(sampler_location, 0);
|
||||
}
|
||||
if (cc_features.used_textures[1]) {
|
||||
GLint sampler_location = glGetUniformLocation(shader_program, "uTex1");
|
||||
glUniform1i(sampler_location, 1);
|
||||
}
|
||||
|
||||
if (cc_features.opt_alpha && cc_features.opt_noise) {
|
||||
prg->frame_count_location = glGetUniformLocation(shader_program, "frame_count");
|
||||
prg->window_height_location = glGetUniformLocation(shader_program, "window_height");
|
||||
prg->used_noise = true;
|
||||
} else {
|
||||
prg->used_noise = false;
|
||||
}
|
||||
|
||||
return prg;
|
||||
}
|
||||
|
||||
static struct ShaderProgram *gfx_opengl_lookup_shader(uint32_t shader_id) {
|
||||
for (size_t i = 0; i < shader_program_pool_size; i++) {
|
||||
if (shader_program_pool[i].shader_id == shader_id) {
|
||||
return &shader_program_pool[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void gfx_opengl_shader_get_info(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]) {
|
||||
*num_inputs = prg->num_inputs;
|
||||
used_textures[0] = prg->used_textures[0];
|
||||
used_textures[1] = prg->used_textures[1];
|
||||
}
|
||||
|
||||
static GLuint gfx_opengl_new_texture(void) {
|
||||
GLuint ret;
|
||||
glGenTextures(1, &ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void gfx_opengl_select_texture(int tile, GLuint texture_id) {
|
||||
glActiveTexture(GL_TEXTURE0 + tile);
|
||||
glBindTexture(GL_TEXTURE_2D, texture_id);
|
||||
}
|
||||
|
||||
static void gfx_opengl_upload_texture(const uint8_t *rgba32_buf, int width, int height) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba32_buf);
|
||||
}
|
||||
|
||||
static uint32_t gfx_cm_to_opengl(uint32_t val) {
|
||||
if (val & G_TX_CLAMP) {
|
||||
return GL_CLAMP_TO_EDGE;
|
||||
}
|
||||
return (val & G_TX_MIRROR) ? GL_MIRRORED_REPEAT : GL_REPEAT;
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint32_t cms, uint32_t cmt) {
|
||||
glActiveTexture(GL_TEXTURE0 + tile);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear_filter ? GL_LINEAR : GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gfx_cm_to_opengl(cms));
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gfx_cm_to_opengl(cmt));
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_depth_test(bool depth_test) {
|
||||
if (depth_test) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_depth_mask(bool z_upd) {
|
||||
glDepthMask(z_upd ? GL_TRUE : GL_FALSE);
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_zmode_decal(bool zmode_decal) {
|
||||
if (zmode_decal) {
|
||||
glPolygonOffset(-2, -2);
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
} else {
|
||||
glPolygonOffset(0, 0);
|
||||
glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_viewport(int x, int y, int width, int height) {
|
||||
glViewport(x, y, width, height);
|
||||
current_height = height;
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_scissor(int x, int y, int width, int height) {
|
||||
glScissor(x, y, width, height);
|
||||
}
|
||||
|
||||
static void gfx_opengl_set_use_alpha(bool use_alpha) {
|
||||
if (use_alpha) {
|
||||
glEnable(GL_BLEND);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_opengl_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) {
|
||||
//printf("flushing %d tris\n", buf_vbo_num_tris);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * buf_vbo_len, buf_vbo, GL_STREAM_DRAW);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris);
|
||||
}
|
||||
|
||||
static void gfx_opengl_init(void) {
|
||||
#if FOR_WINDOWS
|
||||
glewInit();
|
||||
#endif
|
||||
|
||||
glGenBuffers(1, &opengl_vbo);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, opengl_vbo);
|
||||
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
static void gfx_opengl_on_resize(void) {
|
||||
}
|
||||
|
||||
static void gfx_opengl_start_frame(void) {
|
||||
frame_count++;
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDepthMask(GL_TRUE); // Must be set to clear Z-buffer
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
static void gfx_opengl_end_frame(void) {
|
||||
}
|
||||
|
||||
static void gfx_opengl_finish_render(void) {
|
||||
}
|
||||
|
||||
struct GfxRenderingAPI gfx_opengl_api = {
|
||||
gfx_opengl_z_is_from_0_to_1,
|
||||
gfx_opengl_unload_shader,
|
||||
gfx_opengl_load_shader,
|
||||
gfx_opengl_create_and_load_new_shader,
|
||||
gfx_opengl_lookup_shader,
|
||||
gfx_opengl_shader_get_info,
|
||||
gfx_opengl_new_texture,
|
||||
gfx_opengl_select_texture,
|
||||
gfx_opengl_upload_texture,
|
||||
gfx_opengl_set_sampler_parameters,
|
||||
gfx_opengl_set_depth_test,
|
||||
gfx_opengl_set_depth_mask,
|
||||
gfx_opengl_set_zmode_decal,
|
||||
gfx_opengl_set_viewport,
|
||||
gfx_opengl_set_scissor,
|
||||
gfx_opengl_set_use_alpha,
|
||||
gfx_opengl_draw_triangles,
|
||||
gfx_opengl_init,
|
||||
gfx_opengl_on_resize,
|
||||
gfx_opengl_start_frame,
|
||||
gfx_opengl_end_frame,
|
||||
gfx_opengl_finish_render
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef GFX_OPENGL_H
|
||||
#define GFX_OPENGL_H
|
||||
|
||||
#include "gfx_rendering_api.h"
|
||||
|
||||
extern struct GfxRenderingAPI gfx_opengl_api;
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
#ifndef GFX_PC_H
|
||||
#define GFX_PC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct GfxRenderingAPI;
|
||||
struct GfxWindowManagerAPI;
|
||||
|
||||
struct GfxDimensions {
|
||||
uint32_t width, height;
|
||||
float aspect_ratio;
|
||||
};
|
||||
|
||||
extern struct GfxDimensions gfx_current_dimensions;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void gfx_init(struct GfxWindowManagerAPI *wapi, struct GfxRenderingAPI *rapi, const char *game_name, bool start_in_fullscreen);
|
||||
struct GfxRenderingAPI *gfx_get_current_rendering_api(void);
|
||||
void gfx_start_frame(void);
|
||||
void gfx_run(Gfx *commands);
|
||||
void gfx_end_frame(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef GFX_RENDERING_API_H
|
||||
#define GFX_RENDERING_API_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct ShaderProgram;
|
||||
|
||||
struct GfxRenderingAPI {
|
||||
bool (*z_is_from_0_to_1)(void);
|
||||
void (*unload_shader)(struct ShaderProgram *old_prg);
|
||||
void (*load_shader)(struct ShaderProgram *new_prg);
|
||||
struct ShaderProgram *(*create_and_load_new_shader)(uint32_t shader_id);
|
||||
struct ShaderProgram *(*lookup_shader)(uint32_t shader_id);
|
||||
void (*shader_get_info)(struct ShaderProgram *prg, uint8_t *num_inputs, bool used_textures[2]);
|
||||
uint32_t (*new_texture)(void);
|
||||
void (*select_texture)(int tile, uint32_t texture_id);
|
||||
void (*upload_texture)(const uint8_t *rgba32_buf, int width, int height);
|
||||
void (*set_sampler_parameters)(int sampler, bool linear_filter, uint32_t cms, uint32_t cmt);
|
||||
void (*set_depth_test)(bool depth_test);
|
||||
void (*set_depth_mask)(bool z_upd);
|
||||
void (*set_zmode_decal)(bool zmode_decal);
|
||||
void (*set_viewport)(int x, int y, int width, int height);
|
||||
void (*set_scissor)(int x, int y, int width, int height);
|
||||
void (*set_use_alpha)(bool use_alpha);
|
||||
void (*draw_triangles)(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris);
|
||||
void (*init)(void);
|
||||
void (*on_resize)(void);
|
||||
void (*start_frame)(void);
|
||||
void (*end_frame)(void);
|
||||
void (*finish_render)(void);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef GFX_SCREEN_CONFIG_H
|
||||
#define GFX_SCREEN_CONFIG_H
|
||||
|
||||
#define DESIRED_SCREEN_WIDTH 640
|
||||
#define DESIRED_SCREEN_HEIGHT 480
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef GFX_SDL_H
|
||||
#define GFX_SDL_H
|
||||
|
||||
#include "gfx_window_manager_api.h"
|
||||
|
||||
extern struct GfxWindowManagerAPI gfx_sdl;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,315 @@
|
||||
#include "../compat.h"
|
||||
|
||||
#if !defined(__linux__) && !defined(__BSD__) && defined(ENABLE_OPENGL)
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#define FOR_WINDOWS 1
|
||||
#else
|
||||
#define FOR_WINDOWS 0
|
||||
#endif
|
||||
|
||||
#if FOR_WINDOWS
|
||||
#include <GL/glew.h>
|
||||
#include "SDL.h"
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include "SDL_opengl.h"
|
||||
#else
|
||||
#include <SDL2/SDL.h>
|
||||
#define GL_GLEXT_PROTOTYPES 1
|
||||
#include <SDL2/SDL_opengles2.h>
|
||||
#endif
|
||||
|
||||
#include "gfx_window_manager_api.h"
|
||||
#include "gfx_screen_config.h"
|
||||
|
||||
#define GFX_API_NAME "SDL2 - OpenGL"
|
||||
|
||||
static SDL_Window *wnd;
|
||||
static int inverted_scancode_table[512];
|
||||
static int vsync_enabled = 0;
|
||||
static unsigned int window_width = DESIRED_SCREEN_WIDTH;
|
||||
static unsigned int window_height = DESIRED_SCREEN_HEIGHT;
|
||||
static bool fullscreen_state;
|
||||
static void (*on_fullscreen_changed_callback)(bool is_now_fullscreen);
|
||||
static bool (*on_key_down_callback)(int scancode);
|
||||
static bool (*on_key_up_callback)(int scancode);
|
||||
static void (*on_all_keys_up_callback)(void);
|
||||
|
||||
const SDL_Scancode windows_scancode_table[] =
|
||||
{
|
||||
/* 0 1 2 3 4 5 6 7 */
|
||||
/* 8 9 A B C D E F */
|
||||
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_ESCAPE, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4, SDL_SCANCODE_5, SDL_SCANCODE_6, /* 0 */
|
||||
SDL_SCANCODE_7, SDL_SCANCODE_8, SDL_SCANCODE_9, SDL_SCANCODE_0, SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, SDL_SCANCODE_BACKSPACE, SDL_SCANCODE_TAB, /* 0 */
|
||||
|
||||
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R, SDL_SCANCODE_T, SDL_SCANCODE_Y, SDL_SCANCODE_U, SDL_SCANCODE_I, /* 1 */
|
||||
SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_RETURN, SDL_SCANCODE_LCTRL, SDL_SCANCODE_A, SDL_SCANCODE_S, /* 1 */
|
||||
|
||||
SDL_SCANCODE_D, SDL_SCANCODE_F, SDL_SCANCODE_G, SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, SDL_SCANCODE_SEMICOLON, /* 2 */
|
||||
SDL_SCANCODE_APOSTROPHE, SDL_SCANCODE_GRAVE, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V, /* 2 */
|
||||
|
||||
SDL_SCANCODE_B, SDL_SCANCODE_N, SDL_SCANCODE_M, SDL_SCANCODE_COMMA, SDL_SCANCODE_PERIOD, SDL_SCANCODE_SLASH, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_PRINTSCREEN,/* 3 */
|
||||
SDL_SCANCODE_LALT, SDL_SCANCODE_SPACE, SDL_SCANCODE_CAPSLOCK, SDL_SCANCODE_F1, SDL_SCANCODE_F2, SDL_SCANCODE_F3, SDL_SCANCODE_F4, SDL_SCANCODE_F5, /* 3 */
|
||||
|
||||
SDL_SCANCODE_F6, SDL_SCANCODE_F7, SDL_SCANCODE_F8, SDL_SCANCODE_F9, SDL_SCANCODE_F10, SDL_SCANCODE_NUMLOCKCLEAR, SDL_SCANCODE_SCROLLLOCK, SDL_SCANCODE_HOME, /* 4 */
|
||||
SDL_SCANCODE_UP, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_KP_MINUS, SDL_SCANCODE_LEFT, SDL_SCANCODE_KP_5, SDL_SCANCODE_RIGHT, SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_END, /* 4 */
|
||||
|
||||
SDL_SCANCODE_DOWN, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_INSERT, SDL_SCANCODE_DELETE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_NONUSBACKSLASH,SDL_SCANCODE_F11, /* 5 */
|
||||
SDL_SCANCODE_F12, SDL_SCANCODE_PAUSE, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_LGUI, SDL_SCANCODE_RGUI, SDL_SCANCODE_APPLICATION, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 5 */
|
||||
|
||||
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_F13, SDL_SCANCODE_F14, SDL_SCANCODE_F15, SDL_SCANCODE_F16, /* 6 */
|
||||
SDL_SCANCODE_F17, SDL_SCANCODE_F18, SDL_SCANCODE_F19, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 6 */
|
||||
|
||||
SDL_SCANCODE_INTERNATIONAL2, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL1, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN, /* 7 */
|
||||
SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL4, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL5, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_INTERNATIONAL3, SDL_SCANCODE_UNKNOWN, SDL_SCANCODE_UNKNOWN /* 7 */
|
||||
};
|
||||
|
||||
const SDL_Scancode scancode_rmapping_extended[][2] = {
|
||||
{SDL_SCANCODE_KP_ENTER, SDL_SCANCODE_RETURN},
|
||||
{SDL_SCANCODE_RALT, SDL_SCANCODE_LALT},
|
||||
{SDL_SCANCODE_RCTRL, SDL_SCANCODE_LCTRL},
|
||||
{SDL_SCANCODE_KP_DIVIDE, SDL_SCANCODE_SLASH},
|
||||
//{SDL_SCANCODE_KP_PLUS, SDL_SCANCODE_CAPSLOCK}
|
||||
};
|
||||
|
||||
const SDL_Scancode scancode_rmapping_nonextended[][2] = {
|
||||
{SDL_SCANCODE_KP_7, SDL_SCANCODE_HOME},
|
||||
{SDL_SCANCODE_KP_8, SDL_SCANCODE_UP},
|
||||
{SDL_SCANCODE_KP_9, SDL_SCANCODE_PAGEUP},
|
||||
{SDL_SCANCODE_KP_4, SDL_SCANCODE_LEFT},
|
||||
{SDL_SCANCODE_KP_6, SDL_SCANCODE_RIGHT},
|
||||
{SDL_SCANCODE_KP_1, SDL_SCANCODE_END},
|
||||
{SDL_SCANCODE_KP_2, SDL_SCANCODE_DOWN},
|
||||
{SDL_SCANCODE_KP_3, SDL_SCANCODE_PAGEDOWN},
|
||||
{SDL_SCANCODE_KP_0, SDL_SCANCODE_INSERT},
|
||||
{SDL_SCANCODE_KP_PERIOD, SDL_SCANCODE_DELETE},
|
||||
{SDL_SCANCODE_KP_MULTIPLY, SDL_SCANCODE_PRINTSCREEN}
|
||||
};
|
||||
|
||||
static void set_fullscreen(bool on, bool call_callback) {
|
||||
if (fullscreen_state == on) {
|
||||
return;
|
||||
}
|
||||
fullscreen_state = on;
|
||||
|
||||
if (on) {
|
||||
SDL_DisplayMode mode;
|
||||
SDL_GetDesktopDisplayMode(0, &mode);
|
||||
window_width = mode.w;
|
||||
window_height = mode.h;
|
||||
} else {
|
||||
window_width = DESIRED_SCREEN_WIDTH;
|
||||
window_height = DESIRED_SCREEN_HEIGHT;
|
||||
}
|
||||
SDL_SetWindowSize(wnd, window_width, window_height);
|
||||
SDL_SetWindowFullscreen(wnd, on ? SDL_WINDOW_FULLSCREEN : 0);
|
||||
|
||||
if (on_fullscreen_changed_callback != NULL && call_callback) {
|
||||
on_fullscreen_changed_callback(on);
|
||||
}
|
||||
}
|
||||
|
||||
int test_vsync(void) {
|
||||
// Even if SDL_GL_SetSwapInterval succeeds, it doesn't mean that VSync actually works.
|
||||
// A 60 Hz monitor should have a swap interval of 16.67 milliseconds.
|
||||
// Try to detect the length of a vsync by swapping buffers some times.
|
||||
// Since the graphics card may enqueue a fixed number of frames,
|
||||
// first send in four dummy frames to hopefully fill the queue.
|
||||
// This method will fail if the refresh rate is changed, which, in
|
||||
// combination with that we can't control the queue size (i.e. lag)
|
||||
// is a reason this generic SDL2 backend should only be used as last resort.
|
||||
Uint32 start;
|
||||
Uint32 end;
|
||||
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
start = SDL_GetTicks();
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
end = SDL_GetTicks();
|
||||
|
||||
float average = 4.0 * 1000.0 / (end - start);
|
||||
|
||||
vsync_enabled = 1;
|
||||
if (average > 27 && average < 33) {
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
} else if (average > 57 && average < 63) {
|
||||
SDL_GL_SetSwapInterval(2);
|
||||
} else if (average > 86 && average < 94) {
|
||||
SDL_GL_SetSwapInterval(3);
|
||||
} else if (average > 115 && average < 125) {
|
||||
SDL_GL_SetSwapInterval(4);
|
||||
} else {
|
||||
vsync_enabled = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_sdl_init(const char *game_name, bool start_in_fullscreen) {
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
||||
|
||||
char title[512];
|
||||
int len = sprintf(title, "%s (%s)", game_name, GFX_API_NAME);
|
||||
|
||||
wnd = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
window_width, window_height, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
|
||||
|
||||
if (start_in_fullscreen) {
|
||||
set_fullscreen(true, false);
|
||||
}
|
||||
|
||||
SDL_GL_CreateContext(wnd);
|
||||
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
test_vsync();
|
||||
if (!vsync_enabled)
|
||||
puts("Warning: VSync is not enabled or not working. Falling back to timer for synchronization");
|
||||
|
||||
for (size_t i = 0; i < sizeof(windows_scancode_table) / sizeof(SDL_Scancode); i++) {
|
||||
inverted_scancode_table[windows_scancode_table[i]] = i;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(scancode_rmapping_extended) / sizeof(scancode_rmapping_extended[0]); i++) {
|
||||
inverted_scancode_table[scancode_rmapping_extended[i][0]] = inverted_scancode_table[scancode_rmapping_extended[i][1]] + 0x100;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(scancode_rmapping_nonextended) / sizeof(scancode_rmapping_nonextended[0]); i++) {
|
||||
inverted_scancode_table[scancode_rmapping_extended[i][0]] = inverted_scancode_table[scancode_rmapping_extended[i][1]];
|
||||
inverted_scancode_table[scancode_rmapping_extended[i][1]] += 0x100;
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_sdl_set_fullscreen_changed_callback(void (*on_fullscreen_changed)(bool is_now_fullscreen)) {
|
||||
on_fullscreen_changed_callback = on_fullscreen_changed;
|
||||
}
|
||||
|
||||
static void gfx_sdl_set_fullscreen(bool enable) {
|
||||
set_fullscreen(enable, true);
|
||||
}
|
||||
|
||||
static void gfx_sdl_set_keyboard_callbacks(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void)) {
|
||||
on_key_down_callback = on_key_down;
|
||||
on_key_up_callback = on_key_up;
|
||||
on_all_keys_up_callback = on_all_keys_up;
|
||||
}
|
||||
|
||||
static void gfx_sdl_main_loop(void (*run_one_game_iter)(void)) {
|
||||
while (1) {
|
||||
run_one_game_iter();
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_sdl_get_dimensions(uint32_t *width, uint32_t *height) {
|
||||
*width = window_width;
|
||||
*height = window_height;
|
||||
}
|
||||
|
||||
static int translate_scancode(int scancode) {
|
||||
if (scancode < 512) {
|
||||
return inverted_scancode_table[scancode];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_sdl_onkeydown(int scancode) {
|
||||
int key = translate_scancode(scancode);
|
||||
if (on_key_down_callback != NULL) {
|
||||
on_key_down_callback(key);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_sdl_onkeyup(int scancode) {
|
||||
int key = translate_scancode(scancode);
|
||||
if (on_key_up_callback != NULL) {
|
||||
on_key_up_callback(key);
|
||||
}
|
||||
}
|
||||
|
||||
static void gfx_sdl_handle_events(void) {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
#ifndef TARGET_WEB
|
||||
// Scancodes are broken in Emscripten SDL2: https://bugzilla.libsdl.org/show_bug.cgi?id=3259
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.sym == SDLK_F10) {
|
||||
set_fullscreen(!fullscreen_state, true);
|
||||
break;
|
||||
}
|
||||
gfx_sdl_onkeydown(event.key.keysym.scancode);
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
gfx_sdl_onkeyup(event.key.keysym.scancode);
|
||||
break;
|
||||
#endif
|
||||
case SDL_WINDOWEVENT:
|
||||
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
||||
window_width = event.window.data1;
|
||||
window_height = event.window.data2;
|
||||
}
|
||||
break;
|
||||
case SDL_QUIT:
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool gfx_sdl_start_frame(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void sync_framerate_with_timer(void) {
|
||||
// Number of milliseconds a frame should take (30 fps)
|
||||
const Uint32 FRAME_TIME = 1000 / 30;
|
||||
static Uint32 last_time;
|
||||
Uint32 elapsed = SDL_GetTicks() - last_time;
|
||||
|
||||
if (elapsed < FRAME_TIME)
|
||||
SDL_Delay(FRAME_TIME - elapsed);
|
||||
last_time += FRAME_TIME;
|
||||
}
|
||||
|
||||
static void gfx_sdl_swap_buffers_begin(void) {
|
||||
if (!vsync_enabled) {
|
||||
sync_framerate_with_timer();
|
||||
}
|
||||
|
||||
SDL_GL_SwapWindow(wnd);
|
||||
}
|
||||
|
||||
static void gfx_sdl_swap_buffers_end(void) {
|
||||
}
|
||||
|
||||
static double gfx_sdl_get_time(void) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
struct GfxWindowManagerAPI gfx_sdl = {
|
||||
gfx_sdl_init,
|
||||
gfx_sdl_set_keyboard_callbacks,
|
||||
gfx_sdl_set_fullscreen_changed_callback,
|
||||
gfx_sdl_set_fullscreen,
|
||||
gfx_sdl_main_loop,
|
||||
gfx_sdl_get_dimensions,
|
||||
gfx_sdl_handle_events,
|
||||
gfx_sdl_start_frame,
|
||||
gfx_sdl_swap_buffers_begin,
|
||||
gfx_sdl_swap_buffers_end,
|
||||
gfx_sdl_get_time
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef GFX_WINDOW_MANAGER_API_H
|
||||
#define GFX_WINDOW_MANAGER_API_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct GfxWindowManagerAPI {
|
||||
void (*init)(const char *game_name, bool start_in_fullscreen);
|
||||
void (*set_keyboard_callbacks)(bool (*on_key_down)(int scancode), bool (*on_key_up)(int scancode), void (*on_all_keys_up)(void));
|
||||
void (*set_fullscreen_changed_callback)(void (*on_fullscreen_changed)(bool is_now_fullscreen));
|
||||
void (*set_fullscreen)(bool enable);
|
||||
void (*main_loop)(void (*run_one_game_iter)(void));
|
||||
void (*get_dimensions)(uint32_t *width, uint32_t *height);
|
||||
void (*handle_events)(void);
|
||||
bool (*start_frame)(void);
|
||||
void (*swap_buffers_begin)(void);
|
||||
void (*swap_buffers_end)(void);
|
||||
double (*get_time)(void); // For debug
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,127 @@
|
||||
#ifndef _LIBAUDIO_INTERNAL_H_
|
||||
#define _LIBAUDIO_INTERNAL_H_
|
||||
#include <ultra64.h>
|
||||
#define AL_BANK_VERSION 0x4231 /* 'B1' */
|
||||
|
||||
typedef u8 ALPan;
|
||||
typedef s32 ALMicroTime;
|
||||
|
||||
/* Possible wavetable types */
|
||||
enum
|
||||
{
|
||||
AL_ADPCM_WAVE = 0,
|
||||
AL_RAW16_WAVE
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 start;
|
||||
u32 end;
|
||||
u32 count;
|
||||
} ALRawLoop;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u32 start;
|
||||
u32 end;
|
||||
u32 count;
|
||||
ADPCM_STATE state;
|
||||
} ALADPCMloop;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
s32 order;
|
||||
s32 npredictors;
|
||||
s16 book[1]; // variable size, 8-byte aligned
|
||||
} ALADPCMBook;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ALMicroTime attackTime;
|
||||
ALMicroTime decayTime;
|
||||
ALMicroTime releaseTime;
|
||||
u8 attackVolume;
|
||||
u8 decayVolume;
|
||||
} ALEnvelope;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 velocityMin;
|
||||
u8 velocityMax;
|
||||
u8 keyMin;
|
||||
u8 keyMax;
|
||||
u8 keyBase;
|
||||
s8 detune;
|
||||
} ALKeyMap;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ALADPCMloop *loop;
|
||||
ALADPCMBook *book;
|
||||
} ALADPCMWaveInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ALRawLoop *loop;
|
||||
} ALRAWWaveInfo;
|
||||
|
||||
typedef struct ALWaveTable_s
|
||||
{
|
||||
u8 *base; /* ptr to start of wave data */
|
||||
s32 len; /* length of data in bytes */
|
||||
u8 type; /* compression type */
|
||||
u8 flags; /* offset/address flags */
|
||||
union {
|
||||
ALADPCMWaveInfo adpcmWave;
|
||||
ALRAWWaveInfo rawWave;
|
||||
} waveInfo;
|
||||
} ALWaveTable;
|
||||
|
||||
typedef struct ALSound_s
|
||||
{
|
||||
ALEnvelope *envelope;
|
||||
ALKeyMap *keyMap;
|
||||
ALWaveTable *wavetable; /* offset to wavetable struct */
|
||||
ALPan samplePan;
|
||||
u8 sampleVolume;
|
||||
u8 flags;
|
||||
} ALSound;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
u8 volume; /* overall volume for this instrument */
|
||||
ALPan pan; /* 0 = hard left, 127 = hard right */
|
||||
u8 priority; /* voice priority for this instrument */
|
||||
u8 flags;
|
||||
u8 tremType; /* the type of tremelo osc. to use */
|
||||
u8 tremRate; /* the rate of the tremelo osc. */
|
||||
u8 tremDepth; /* the depth of the tremelo osc */
|
||||
u8 tremDelay; /* the delay for the tremelo osc */
|
||||
u8 vibType; /* the type of tremelo osc. to use */
|
||||
u8 vibRate; /* the rate of the tremelo osc. */
|
||||
u8 vibDepth; /* the depth of the tremelo osc */
|
||||
u8 vibDelay; /* the delay for the tremelo osc */
|
||||
s16 bendRange; /* pitch bend range in cents */
|
||||
s16 soundCount; /* number of sounds in this array */
|
||||
ALSound *soundArray[1];
|
||||
} ALInstrument;
|
||||
|
||||
typedef struct ALBank_s
|
||||
{
|
||||
s16 instCount; /* number of programs in this bank */
|
||||
u8 flags;
|
||||
u8 pad;
|
||||
s32 sampleRate; /* e.g. 44100, 22050, etc... */
|
||||
ALInstrument *percussion; /* default percussion for GM */
|
||||
ALInstrument *instArray[1]; /* ARRAY of instruments */
|
||||
} ALBank;
|
||||
|
||||
typedef struct
|
||||
{ /* Note: sizeof won't be correct */
|
||||
s16 revision; /* format revision of this file */
|
||||
s16 bankCount; /* number of banks */
|
||||
ALBank *bankArray[1]; /* ARRAY of bank offsets */
|
||||
} ALBankFile;
|
||||
|
||||
void alBnkfNew(ALBankFile *f, u8 *table);
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef _LIBULTRA_INTERNAL_H_
|
||||
#define _LIBULTRA_INTERNAL_H_
|
||||
#include <ultra64.h>
|
||||
|
||||
/*
|
||||
* This define is needed because the original definitions in __osDequeueThread.c are declared
|
||||
* seperately instead of part of a single struct, however some code alises over this memory
|
||||
* assuming a unified structure. To fix this, we declare the full type here and then alias the
|
||||
* symbol names to the correct members in AVOID_UB.
|
||||
*/
|
||||
#ifdef AVOID_UB
|
||||
typedef struct OSThread_ListHead_s
|
||||
{
|
||||
/*0x00*/ struct OSThread_s *next;
|
||||
/*0x04*/ OSPri priority;
|
||||
/*0x08*/ struct OSThread_s *queue;
|
||||
/*0x0C*/ struct OSThread_s *tlnext;
|
||||
/*0x10*/ struct OSThread_s *unk10;
|
||||
/*0x14*/ u32 unk14;
|
||||
} OSThread_ListHead;
|
||||
|
||||
// Now fix the symbols to the new one.
|
||||
extern OSThread_ListHead D_80334890_fix;
|
||||
|
||||
#define D_80334890 D_80334890_fix.next
|
||||
#define D_80334894 D_80334890_fix.priority
|
||||
#define D_80334898 D_80334890_fix.queue
|
||||
#define D_8033489C D_80334890_fix.tlnext
|
||||
#define D_803348A0 D_80334890_fix.unk10
|
||||
|
||||
// Fix for the EEPROM array.
|
||||
extern u32 D_80365E00[16];
|
||||
|
||||
// alias the last array element correctly
|
||||
#define D_80365E3C D_80365E00[15]
|
||||
#else
|
||||
// Original OSThread_ListHead definitions
|
||||
extern OSThread *D_80334890;
|
||||
extern u32 D_80334894;
|
||||
extern OSThread *D_80334898;
|
||||
extern OSThread *D_8033489C;
|
||||
extern OSThread *D_803348A0;
|
||||
|
||||
// Original EEPROM definitions
|
||||
extern u32 D_80365E00[15];
|
||||
extern u32 D_80365E3C;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
u32 initialized; // probably something like initialized?
|
||||
OSThread *mgrThread;
|
||||
OSMesgQueue *cmdQueue;
|
||||
OSMesgQueue *eventQueue;
|
||||
OSMesgQueue *accessQueue;
|
||||
s32 (*dma_func)(s32, u32, void *, size_t);
|
||||
#if defined(VERSION_EU) || defined(VERSION_SH)
|
||||
s32 (*edma_func)(OSPiHandle*, s32, u32, void *, size_t);
|
||||
#else
|
||||
u64 force_align;
|
||||
#endif
|
||||
} OSMgrArgs;
|
||||
|
||||
s32 __osDisableInt(void);
|
||||
void __osRestoreInt(s32);
|
||||
void __osEnqueueAndYield(OSThread **);
|
||||
void __osDequeueThread(OSThread **, OSThread *);
|
||||
void __osEnqueueThread(OSThread **, OSThread *);
|
||||
OSThread *__osPopThread(OSThread **);
|
||||
s32 __osSiRawStartDma(s32, void *);
|
||||
void __osSiCreateAccessQueue(void);
|
||||
void __osSiGetAccess(void);
|
||||
void __osSiRelAccess(void);
|
||||
u32 __osProbeTLB(void *);
|
||||
void __osPiCreateAccessQueue(void);
|
||||
void __osPiGetAccess(void);
|
||||
void __osSetSR(u32);
|
||||
u32 __osGetSR(void);
|
||||
void __osSetFpcCsr(u32);
|
||||
s32 __osSiRawReadIo(void *, u32 *);
|
||||
s32 __osSiRawWriteIo(void *, u32);
|
||||
s32 osPiRawReadIo(u32 a0, u32 *a1);
|
||||
void __osSpSetStatus(u32);
|
||||
u32 __osSpGetStatus(void);
|
||||
s32 __osSpSetPc(void *);
|
||||
s32 __osSpDeviceBusy(void);
|
||||
s32 __osSiDeviceBusy(void);
|
||||
s32 __osSpRawStartDma(u32 dir, void *sp_ptr, void *dram_ptr, size_t size);
|
||||
void __osViInit(void);
|
||||
OSViContext *__osViGetCurrentContext(void);
|
||||
OSViContext *__osViGetCurrentContext2(void);
|
||||
void __osViSwapContext(void);
|
||||
void __osSetTimerIntr(u64);
|
||||
u64 __osInsertTimer(OSTimer *);
|
||||
void __osSetCompare(u32);
|
||||
s32 __osAiDeviceBusy(void);
|
||||
void __osDispatchThread(void);
|
||||
u32 __osGetCause(void);
|
||||
s32 __osAtomicDec(u32 *);
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
#ifndef MIXER_H
|
||||
#define MIXER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <ultra64.h>
|
||||
|
||||
#ifdef VERSION_SH
|
||||
#define NEW_AUDIO_UCODE
|
||||
#endif
|
||||
|
||||
#undef aSegment
|
||||
#undef aClearBuffer
|
||||
#undef aSetBuffer
|
||||
#undef aLoadBuffer
|
||||
#undef aSaveBuffer
|
||||
#undef aDMEMMove
|
||||
#undef aMix
|
||||
#undef aEnvMixer
|
||||
#undef aResample
|
||||
#undef aInterleave
|
||||
#undef aSetVolume
|
||||
#undef aSetVolume32
|
||||
#undef aSetLoop
|
||||
#undef aLoadADPCM
|
||||
#undef aADPCMdec
|
||||
#undef aS8Dec
|
||||
#undef aAddMixer
|
||||
#undef aDuplicate
|
||||
#undef aDMEMMove2
|
||||
#undef aResampleZoh
|
||||
#undef aDownsampleHalf
|
||||
#undef aEnvSetup1
|
||||
#undef aEnvSetup2
|
||||
#undef aFilter
|
||||
#undef aHiLoGain
|
||||
#undef aUnknown25
|
||||
|
||||
void aClearBufferImpl(uint16_t addr, int nbytes);
|
||||
void aLoadADPCMImpl(int num_entries_times_16, const int16_t *book_source_addr);
|
||||
void aSetBufferImpl(uint8_t flags, uint16_t in, uint16_t out, uint16_t nbytes);
|
||||
void aDMEMMoveImpl(uint16_t in_addr, uint16_t out_addr, int nbytes);
|
||||
void aSetLoopImpl(ADPCM_STATE *adpcm_loop_state);
|
||||
void aADPCMdecImpl(uint8_t flags, ADPCM_STATE state);
|
||||
void aResampleImpl(uint8_t flags, uint16_t pitch, RESAMPLE_STATE state);
|
||||
|
||||
#ifndef NEW_AUDIO_UCODE
|
||||
void aSetVolumeImpl(uint8_t flags, int16_t v, int16_t t, int16_t r);
|
||||
void aLoadBufferImpl(const void *source_addr);
|
||||
void aSaveBufferImpl(int16_t *dest_addr);
|
||||
void aInterleaveImpl(uint16_t left, uint16_t right);
|
||||
void aMixImpl(int16_t gain, uint16_t in_addr, uint16_t out_addr);
|
||||
void aEnvMixerImpl(uint8_t flags, ENVMIX_STATE state);
|
||||
#else
|
||||
void aLoadBufferImpl(const void *source_addr, uint16_t dest_addr, uint16_t nbytes);
|
||||
void aSaveBufferImpl(uint16_t source_addr, int16_t *dest_addr, uint16_t nbytes);
|
||||
void aInterleaveImpl(uint16_t dest, uint16_t left, uint16_t right, uint16_t c);
|
||||
void aMixImpl(int16_t gain, uint16_t in_addr, uint16_t out_addr, uint16_t count);
|
||||
void aEnvSetup1Impl(uint8_t initial_vol_wet, uint16_t rate_wet, uint16_t rate_left, uint16_t rate_right);
|
||||
void aEnvSetup2Impl(uint16_t initial_vol_left, uint16_t initial_vol_right);
|
||||
void aEnvMixerImpl(uint16_t in_addr, uint16_t n_samples, bool swap_reverb,
|
||||
bool neg_left, bool neg_right,
|
||||
uint16_t dry_left_addr, uint16_t dry_right_addr,
|
||||
uint16_t wet_left_addr, uint16_t wet_right_addr);
|
||||
void aS8DecImpl(uint8_t flags, ADPCM_STATE state);
|
||||
void aAddMixerImpl(uint16_t in_addr, uint16_t out_addr, uint16_t count);
|
||||
void aDuplicateImpl(uint16_t in_addr, uint16_t out_addr, uint16_t count);
|
||||
void aDMEMMove2Impl(uint8_t t, uint16_t in_addr, uint16_t out_addr, uint16_t count);
|
||||
void aResampleZohImpl(uint16_t pitch, uint16_t start_fract);
|
||||
void aDownsampleHalfImpl(uint16_t n_samples, uint16_t in_addr, uint16_t out_addr);
|
||||
void aFilterImpl(uint8_t flags, uint16_t count_or_buf, int16_t state_or_filter[8]);
|
||||
void aHiLoGainImpl(uint8_t g, uint16_t count, uint16_t addr);
|
||||
void aUnknown25Impl(uint8_t f, uint16_t count, uint16_t out_addr, uint16_t in_addr);
|
||||
#endif
|
||||
|
||||
#define aSegment(pkt, s, b) do { } while(0)
|
||||
#define aClearBuffer(pkt, d, c) aClearBufferImpl(d, c)
|
||||
#define aLoadADPCM(pkt, c, d) aLoadADPCMImpl(c, d)
|
||||
#define aSetBuffer(pkt, f, i, o, c) aSetBufferImpl(f, i, o, c)
|
||||
#define aDMEMMove(pkt, i, o, c) aDMEMMoveImpl(i, o, c)
|
||||
#define aSetLoop(pkt, a) aSetLoopImpl(a)
|
||||
#define aADPCMdec(pkt, f, s) aADPCMdecImpl(f, s)
|
||||
#define aResample(pkt, f, p, s) aResampleImpl(f, p, s)
|
||||
|
||||
#ifndef NEW_AUDIO_UCODE
|
||||
#define aSetVolume(pkt, f, v, t, r) aSetVolumeImpl(f, v, t, r)
|
||||
#define aSetVolume32(pkt, f, v, tr) aSetVolume(pkt, f, v, (int16_t)((tr) >> 16), (int16_t)(tr))
|
||||
#define aLoadBuffer(pkt, s) aLoadBufferImpl(s)
|
||||
#define aSaveBuffer(pkt, s) aSaveBufferImpl(s)
|
||||
#define aInterleave(pkt, l, r) aInterleaveImpl(l, r)
|
||||
#define aMix(pkt, f, g, i, o) aMixImpl(g, i, o)
|
||||
#define aEnvMixer(pkt, f, s) aEnvMixerImpl(f, s)
|
||||
#else
|
||||
#define aLoadBuffer(pkt, s, d, c) aLoadBufferImpl(s, d, c)
|
||||
#define aSaveBuffer(pkt, s, d, c) aSaveBufferImpl(s, d, c)
|
||||
#define aInterleave(pkt, o, l, r, c) aInterleaveImpl(o, l, r, c)
|
||||
#define aMix(pkt, g, i, o, c) aMixImpl(g, i, o, c)
|
||||
#define aEnvSetup1(pkt, initialVolReverb, rampReverb, rampLeft, rampRight) \
|
||||
aEnvSetup1Impl(initialVolReverb, rampReverb, rampLeft, rampRight)
|
||||
#define aEnvSetup2(pkt, initialVolLeft, initialVolRight) \
|
||||
aEnvSetup2Impl(initialVolLeft, initialVolRight)
|
||||
#define aEnvMixer(pkt, inBuf, nSamples, swapReverb, negLeft, negRight, \
|
||||
dryLeft, dryRight, wetLeft, wetRight) \
|
||||
aEnvMixerImpl(inBuf, nSamples, swapReverb, negLeft, negRight, \
|
||||
dryLeft, dryRight, wetLeft, wetRight)
|
||||
#define aS8Dec(pkt, f, s) aS8DecImpl(f, s)
|
||||
#define aAddMixer(pkt, s, d, c) aAddMixerImpl(s, d, c)
|
||||
#define aDuplicate(pkt, s, d, c) aDuplicateImpl(s, d, c)
|
||||
#define aDMEMMove2(pkt, t, i, o, c) aDMEMMove2Impl(t, i, o, c)
|
||||
#define aResampleZoh(pkt, pitch, startFract) aResampleZohImpl(pitch, startFract)
|
||||
#define aDownsampleHalf(pkt, nSamples, i, o) aDownsampleHalfImpl(nSamples, i, o)
|
||||
#define aFilter(pkt, f, countOrBuf, addr) aFilterImpl(f, countOrBuf, addr)
|
||||
#define aHiLoGain(pkt, g, buflen, i) aHiLoGainImpl(g, buflen, i)
|
||||
#define aUnknown25(pkt, f, c, o, i) aUnknown25Impl(f, c, o, i)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,216 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "libultra_internal.h"
|
||||
#include <macros.h>
|
||||
|
||||
#ifdef TARGET_WEB
|
||||
#include <emscripten.h>
|
||||
#endif
|
||||
|
||||
extern OSMgrArgs piMgrArgs;
|
||||
|
||||
u64 osClockRate = 62500000;
|
||||
|
||||
s32 osPiStartDma(UNUSED OSIoMesg *mb, UNUSED s32 priority, UNUSED s32 direction,
|
||||
uintptr_t devAddr, void *vAddr, size_t nbytes,
|
||||
UNUSED OSMesgQueue *mq) {
|
||||
memcpy(vAddr, (const void *) devAddr, nbytes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osCreateMesgQueue(OSMesgQueue *mq, OSMesg *msgBuf, s32 count) {
|
||||
mq->validCount = 0;
|
||||
mq->first = 0;
|
||||
mq->msgCount = count;
|
||||
mq->msg = msgBuf;
|
||||
return;
|
||||
}
|
||||
|
||||
void osSetEventMesg(UNUSED OSEvent e, UNUSED OSMesgQueue *mq, UNUSED OSMesg msg) {
|
||||
}
|
||||
s32 osJamMesg(UNUSED OSMesgQueue *mq, UNUSED OSMesg msg, UNUSED s32 flag) {
|
||||
return 0;
|
||||
}
|
||||
s32 osSendMesg(UNUSED OSMesgQueue *mq, UNUSED OSMesg msg, UNUSED s32 flag) {
|
||||
#if defined(VERSION_EU) || defined(VERSION_SH)
|
||||
s32 index;
|
||||
if (mq->validCount >= mq->msgCount) {
|
||||
return -1;
|
||||
}
|
||||
index = (mq->first + mq->validCount) % mq->msgCount;
|
||||
mq->msg[index] = msg;
|
||||
mq->validCount++;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
s32 osRecvMesg(UNUSED OSMesgQueue *mq, UNUSED OSMesg *msg, UNUSED s32 flag) {
|
||||
#if defined(VERSION_EU) || defined(VERSION_SH)
|
||||
if (mq->validCount == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (msg != NULL) {
|
||||
*msg = *(mq->first + mq->msg);
|
||||
}
|
||||
mq->first = (mq->first + 1) % mq->msgCount;
|
||||
mq->validCount--;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t osVirtualToPhysical(void *addr) {
|
||||
return (uintptr_t) addr;
|
||||
}
|
||||
|
||||
void osCreateViManager(UNUSED OSPri pri) {
|
||||
}
|
||||
void osViSetMode(UNUSED OSViMode *mode) {
|
||||
}
|
||||
void osViSetEvent(UNUSED OSMesgQueue *mq, UNUSED OSMesg msg, UNUSED u32 retraceCount) {
|
||||
}
|
||||
void osViBlack(UNUSED u8 active) {
|
||||
}
|
||||
void osViSetSpecialFeatures(UNUSED u32 func) {
|
||||
}
|
||||
void osViSwapBuffer(UNUSED void *vaddr) {
|
||||
}
|
||||
|
||||
OSTime osGetTime(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void osWritebackDCacheAll(void) {
|
||||
}
|
||||
|
||||
void osWritebackDCache(UNUSED void *a, UNUSED size_t b) {
|
||||
}
|
||||
|
||||
void osInvalDCache(UNUSED void *a, UNUSED size_t b) {
|
||||
}
|
||||
|
||||
u32 osGetCount(void) {
|
||||
static u32 counter;
|
||||
return counter++;
|
||||
}
|
||||
|
||||
s32 osAiSetFrequency(u32 freq) {
|
||||
u32 a1;
|
||||
s32 a2;
|
||||
u32 D_8033491C;
|
||||
|
||||
#ifdef VERSION_EU
|
||||
D_8033491C = 0x02E6025C;
|
||||
#else
|
||||
D_8033491C = 0x02E6D354;
|
||||
#endif
|
||||
|
||||
a1 = D_8033491C / (float) freq + .5f;
|
||||
|
||||
if (a1 < 0x84) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
a2 = (a1 / 66) & 0xff;
|
||||
if (a2 > 16) {
|
||||
a2 = 16;
|
||||
}
|
||||
|
||||
return D_8033491C / (s32) a1;
|
||||
}
|
||||
|
||||
s32 osEepromProbe(UNUSED OSMesgQueue *mq) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) {
|
||||
u8 content[512];
|
||||
s32 ret = -1;
|
||||
|
||||
#ifdef TARGET_WEB
|
||||
if (EM_ASM_INT({
|
||||
var s = localStorage.sm64_save_file;
|
||||
if (s && s.length === 684) {
|
||||
try {
|
||||
var binary = atob(s);
|
||||
if (binary.length === 512) {
|
||||
for (var i = 0; i < 512; i++) {
|
||||
HEAPU8[$0 + i] = binary.charCodeAt(i);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}, content)) {
|
||||
memcpy(buffer, content + address * 8, nbytes);
|
||||
ret = 0;
|
||||
}
|
||||
#else
|
||||
FILE *fp = fopen("sm64_save_file.bin", "rb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
if (fread(content, 1, 512, fp) == 512) {
|
||||
memcpy(buffer, content + address * 8, nbytes);
|
||||
ret = 0;
|
||||
}
|
||||
fclose(fp);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) {
|
||||
u8 content[512] = {0};
|
||||
if (address != 0 || nbytes != 512) {
|
||||
osEepromLongRead(mq, 0, content, 512);
|
||||
}
|
||||
memcpy(content + address * 8, buffer, nbytes);
|
||||
|
||||
#ifdef TARGET_WEB
|
||||
EM_ASM({
|
||||
var str = "";
|
||||
for (var i = 0; i < 512; i++) {
|
||||
str += String.fromCharCode(HEAPU8[$0 + i]);
|
||||
}
|
||||
localStorage.sm64_save_file = btoa(str);
|
||||
}, content);
|
||||
s32 ret = 0;
|
||||
#else
|
||||
FILE* fp = fopen("sm64_save_file.bin", "wb");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
s32 ret = fwrite(content, 1, 512, fp) == 512 ? 0 : -1;
|
||||
fclose(fp);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
s32 gNumVblanks;
|
||||
|
||||
s32 osMotorInit(UNUSED OSMesgQueue *mq, UNUSED void *pfs, UNUSED int channel) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 osMotorStart(UNUSED void *pfs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
s32 osMotorStop(UNUSED void *pfs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
OSPiHandle *osCartRomInit(void) {
|
||||
static OSPiHandle handle;
|
||||
return &handle;
|
||||
}
|
||||
|
||||
OSPiHandle *osDriveRomInit(void) {
|
||||
static OSPiHandle handle;
|
||||
return &handle;
|
||||
}
|
||||
|
||||
s32 osEPiStartDma(UNUSED OSPiHandle *pihandle, OSIoMesg *mb, UNUSED s32 direction) {
|
||||
memcpy(mb->dramAddr, (const void *) mb->devAddr, mb->size);
|
||||
osSendMesg(mb->hdr.retQueue, mb, OS_MESG_NOBLOCK);
|
||||
}
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "load_surfaces.h"
|
||||
#include "gfx_adapter.h"
|
||||
#include "load_anim_data.h"
|
||||
#include "load_audio_data.h"
|
||||
#include "load_tex_data.h"
|
||||
#include "obj_pool.h"
|
||||
|
||||
@@ -134,6 +135,10 @@ SM64_LIB_FN void sm64_global_terminate( void )
|
||||
memory_terminate();
|
||||
}
|
||||
|
||||
SM64_LIB_FN struct AudioBanks sm64_asset_load_audio_banks( void ) {
|
||||
return load_audio_banks();
|
||||
}
|
||||
|
||||
SM64_LIB_FN void sm64_static_surfaces_load( const struct SM64Surface *surfaceArray, uint32_t numSurfaces )
|
||||
{
|
||||
surfaces_load_static( surfaceArray, numSurfaces );
|
||||
|
||||
@@ -80,6 +80,8 @@ extern SM64_LIB_FN void sm64_register_play_sound_function( SM64PlaySoundFunction
|
||||
extern SM64_LIB_FN void sm64_global_init( uint8_t *rom, uint8_t *outTexture );
|
||||
extern SM64_LIB_FN void sm64_global_terminate( void );
|
||||
|
||||
extern SM64_LIB_FN struct AudioBanks sm64_asset_load_audio_banks( void );
|
||||
|
||||
extern SM64_LIB_FN void sm64_static_surfaces_load( const struct SM64Surface *surfaceArray, uint32_t numSurfaces );
|
||||
|
||||
extern SM64_LIB_FN int32_t sm64_mario_create( float x, float y, float z );
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "load_audio_data.h"
|
||||
|
||||
#include "decomp/audio/load.h"
|
||||
|
||||
extern struct AudioBanks load_audio_banks() {
|
||||
int numBanks = 10;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
bank_load_immediate(i, 2);
|
||||
}
|
||||
|
||||
struct AudioBanks audioBanks;
|
||||
audioBanks.numCtlEntries = numBanks;
|
||||
audioBanks.ctlEntries = gCtlEntries;
|
||||
|
||||
return audioBanks;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
struct AudioBanks {
|
||||
int numCtlEntries;
|
||||
struct CtlEntry * ctlEntries;
|
||||
};
|
||||
|
||||
extern struct AudioBanks load_audio_banks();
|
||||
Reference in New Issue
Block a user