515 lines
17 KiB
C
515 lines
17 KiB
C
#if defined(WIN32)
|
|
#pragma comment(lib, "winmm.lib")
|
|
#include <Windows.h>
|
|
#include <mmeapi.h>
|
|
#elif defined(__APPLE__)
|
|
#include <AudioToolbox/AudioToolbox.h>
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#endif
|
|
#include <SDL.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <memory.h>
|
|
|
|
#if 1 // Use the PureDOOM.h single header
|
|
#define DOOM_IMPLEMENTATION
|
|
#include "../../PureDOOM.h"
|
|
#else
|
|
#include "DOOM/DOOM.h"
|
|
#endif
|
|
|
|
|
|
// Palette experiments
|
|
//#define PICO8 1
|
|
//#define CGA 1
|
|
//#define GAME_BOY 1
|
|
|
|
|
|
// Resolution DOOM renders at
|
|
#define WIDTH 320
|
|
#define HEIGHT 200
|
|
#define SCALE 4
|
|
|
|
// Resolution of the SDL window
|
|
#define FULL_WIDTH (WIDTH * SCALE)
|
|
#define FULL_HEIGHT (int)(HEIGHT * 1.2 * SCALE) // 1.2x higher than DOOM's height. Original game was designed stretched
|
|
|
|
|
|
doom_key_t sdl_scancode_to_doom_key(SDL_Scancode scancode)
|
|
{
|
|
switch (scancode)
|
|
{
|
|
case SDL_SCANCODE_TAB: return DOOM_KEY_TAB;
|
|
case SDL_SCANCODE_RETURN: return DOOM_KEY_ENTER;
|
|
case SDL_SCANCODE_ESCAPE: return DOOM_KEY_ESCAPE;
|
|
case SDL_SCANCODE_SPACE: return DOOM_KEY_SPACE;
|
|
case SDL_SCANCODE_APOSTROPHE: return DOOM_KEY_APOSTROPHE;
|
|
case SDL_SCANCODE_KP_MULTIPLY: return DOOM_KEY_MULTIPLY;
|
|
case SDL_SCANCODE_COMMA: return DOOM_KEY_COMMA;
|
|
case SDL_SCANCODE_MINUS: return DOOM_KEY_MINUS;
|
|
case SDL_SCANCODE_PERIOD: return DOOM_KEY_PERIOD;
|
|
case SDL_SCANCODE_SLASH: return DOOM_KEY_SLASH;
|
|
case SDL_SCANCODE_0: return DOOM_KEY_0;
|
|
case SDL_SCANCODE_1: return DOOM_KEY_1;
|
|
case SDL_SCANCODE_2: return DOOM_KEY_2;
|
|
case SDL_SCANCODE_3: return DOOM_KEY_3;
|
|
case SDL_SCANCODE_4: return DOOM_KEY_4;
|
|
case SDL_SCANCODE_5: return DOOM_KEY_5;
|
|
case SDL_SCANCODE_6: return DOOM_KEY_6;
|
|
case SDL_SCANCODE_7: return DOOM_KEY_7;
|
|
case SDL_SCANCODE_8: return DOOM_KEY_8;
|
|
case SDL_SCANCODE_9: return DOOM_KEY_9;
|
|
case SDL_SCANCODE_SEMICOLON: return DOOM_KEY_SEMICOLON;
|
|
case SDL_SCANCODE_EQUALS: return DOOM_KEY_EQUALS;
|
|
case SDL_SCANCODE_LEFTBRACKET: return DOOM_KEY_LEFT_BRACKET;
|
|
case SDL_SCANCODE_RIGHTBRACKET: return DOOM_KEY_RIGHT_BRACKET;
|
|
case SDL_SCANCODE_A: return DOOM_KEY_A;
|
|
case SDL_SCANCODE_B: return DOOM_KEY_B;
|
|
case SDL_SCANCODE_C: return DOOM_KEY_C;
|
|
case SDL_SCANCODE_D: return DOOM_KEY_D;
|
|
case SDL_SCANCODE_E: return DOOM_KEY_E;
|
|
case SDL_SCANCODE_F: return DOOM_KEY_F;
|
|
case SDL_SCANCODE_G: return DOOM_KEY_G;
|
|
case SDL_SCANCODE_H: return DOOM_KEY_H;
|
|
case SDL_SCANCODE_I: return DOOM_KEY_I;
|
|
case SDL_SCANCODE_J: return DOOM_KEY_J;
|
|
case SDL_SCANCODE_K: return DOOM_KEY_K;
|
|
case SDL_SCANCODE_L: return DOOM_KEY_L;
|
|
case SDL_SCANCODE_M: return DOOM_KEY_M;
|
|
case SDL_SCANCODE_N: return DOOM_KEY_N;
|
|
case SDL_SCANCODE_O: return DOOM_KEY_O;
|
|
case SDL_SCANCODE_P: return DOOM_KEY_P;
|
|
case SDL_SCANCODE_Q: return DOOM_KEY_Q;
|
|
case SDL_SCANCODE_R: return DOOM_KEY_R;
|
|
case SDL_SCANCODE_S: return DOOM_KEY_S;
|
|
case SDL_SCANCODE_T: return DOOM_KEY_T;
|
|
case SDL_SCANCODE_U: return DOOM_KEY_U;
|
|
case SDL_SCANCODE_V: return DOOM_KEY_V;
|
|
case SDL_SCANCODE_W: return DOOM_KEY_W;
|
|
case SDL_SCANCODE_X: return DOOM_KEY_X;
|
|
case SDL_SCANCODE_Y: return DOOM_KEY_Y;
|
|
case SDL_SCANCODE_Z: return DOOM_KEY_Z;
|
|
case SDL_SCANCODE_BACKSPACE: return DOOM_KEY_BACKSPACE;
|
|
case SDL_SCANCODE_LCTRL:
|
|
case SDL_SCANCODE_RCTRL: return DOOM_KEY_CTRL;
|
|
case SDL_SCANCODE_LEFT: return DOOM_KEY_LEFT_ARROW;
|
|
case SDL_SCANCODE_UP: return DOOM_KEY_UP_ARROW;
|
|
case SDL_SCANCODE_RIGHT: return DOOM_KEY_RIGHT_ARROW;
|
|
case SDL_SCANCODE_DOWN: return DOOM_KEY_DOWN_ARROW;
|
|
case SDL_SCANCODE_LSHIFT:
|
|
case SDL_SCANCODE_RSHIFT: return DOOM_KEY_SHIFT;
|
|
case SDL_SCANCODE_LALT:
|
|
case SDL_SCANCODE_RALT: return DOOM_KEY_ALT;
|
|
case SDL_SCANCODE_F1: return DOOM_KEY_F1;
|
|
case SDL_SCANCODE_F2: return DOOM_KEY_F2;
|
|
case SDL_SCANCODE_F3: return DOOM_KEY_F3;
|
|
case SDL_SCANCODE_F4: return DOOM_KEY_F4;
|
|
case SDL_SCANCODE_F5: return DOOM_KEY_F5;
|
|
case SDL_SCANCODE_F6: return DOOM_KEY_F6;
|
|
case SDL_SCANCODE_F7: return DOOM_KEY_F7;
|
|
case SDL_SCANCODE_F8: return DOOM_KEY_F8;
|
|
case SDL_SCANCODE_F9: return DOOM_KEY_F9;
|
|
case SDL_SCANCODE_F10: return DOOM_KEY_F10;
|
|
case SDL_SCANCODE_F11: return DOOM_KEY_F11;
|
|
case SDL_SCANCODE_F12: return DOOM_KEY_F12;
|
|
case SDL_SCANCODE_PAUSE: return DOOM_KEY_PAUSE;
|
|
default: return DOOM_KEY_UNKNOWN;
|
|
}
|
|
return DOOM_KEY_UNKNOWN;
|
|
}
|
|
|
|
|
|
doom_button_t sdl_button_to_doom_button(Uint8 sdl_button)
|
|
{
|
|
switch (sdl_button)
|
|
{
|
|
case SDL_BUTTON_LEFT: return DOOM_LEFT_BUTTON;
|
|
case SDL_BUTTON_RIGHT: return DOOM_RIGHT_BUTTON;
|
|
case SDL_BUTTON_MIDDLE: return DOOM_MIDDLE_BUTTON;
|
|
}
|
|
return (doom_button_t)0;
|
|
}
|
|
|
|
|
|
void audio_callback(void* userdata, Uint8* stream, int len)
|
|
{
|
|
SDL_LockAudio();
|
|
int16_t* buffer = doom_get_sound_buffer();
|
|
SDL_UnlockAudio();
|
|
|
|
memcpy(stream, buffer, len);
|
|
}
|
|
|
|
|
|
#if defined(WIN32)
|
|
static HMIDIOUT midi_out_handle = 0;
|
|
void send_midi_msg(uint32_t midi_msg)
|
|
{
|
|
if (midi_out_handle)
|
|
{
|
|
midiOutShortMsg(midi_out_handle, midi_msg);
|
|
}
|
|
}
|
|
#elif defined(__APPLE__)
|
|
AudioUnit audio_unit = 0;
|
|
void send_midi_msg(uint32_t midi_msg)
|
|
{
|
|
if (audio_unit)
|
|
{
|
|
MusicDeviceMIDIEvent(audio_unit,
|
|
(midi_msg) & 0xFF,
|
|
(midi_msg >> 8) & 0xFF,
|
|
(midi_msg >> 16) & 0xFF,
|
|
0);
|
|
}
|
|
}
|
|
#else
|
|
void send_midi_msg(uint32_t midi_msg) {}
|
|
#endif
|
|
|
|
|
|
SDL_TimerID midi_timer = 0;
|
|
Uint32 tick_midi(Uint32 interval, void *param)
|
|
{
|
|
uint32_t midi_msg;
|
|
|
|
SDL_LockAudio();
|
|
while ((midi_msg = doom_tick_midi()) != 0) send_midi_msg(midi_msg);
|
|
SDL_UnlockAudio();
|
|
|
|
#if defined(__APPLE__)
|
|
return 1000 / DOOM_MIDI_RATE - 1; // Weirdly, on Apple music is too slow
|
|
#else
|
|
return 1000 / DOOM_MIDI_RATE;
|
|
#endif
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
// Init SDL
|
|
SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_TIMER);
|
|
SDL_Window* window = SDL_CreateWindow("Pure DOOM - SDL Example",
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
FULL_WIDTH, FULL_HEIGHT,
|
|
SDL_WINDOW_RESIZABLE);
|
|
|
|
// SDL Renderer
|
|
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE);
|
|
SDL_Texture* render_target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
|
|
|
|
// SDL Audio thread
|
|
SDL_AudioSpec audio_spec;
|
|
memset(&audio_spec, 0, sizeof(audio_spec));
|
|
audio_spec.freq = DOOM_SAMPLERATE;
|
|
audio_spec.format = AUDIO_S16;
|
|
audio_spec.channels = 2;
|
|
audio_spec.samples = 512;
|
|
audio_spec.callback = audio_callback;
|
|
|
|
if (SDL_OpenAudio(&audio_spec, NULL) < 0)
|
|
{
|
|
printf("Failed to SDL_OpenAudio\n");
|
|
return 1;
|
|
}
|
|
|
|
// Capture mouse
|
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
|
|
|
//-----------------------------------------------------------------------
|
|
// Setup DOOM
|
|
//-----------------------------------------------------------------------
|
|
|
|
// Change default bindings to modern
|
|
doom_set_default_int("key_up", DOOM_KEY_W);
|
|
doom_set_default_int("key_down", DOOM_KEY_S);
|
|
doom_set_default_int("key_strafeleft", DOOM_KEY_A);
|
|
doom_set_default_int("key_straferight", DOOM_KEY_D);
|
|
doom_set_default_int("key_use", DOOM_KEY_E);
|
|
doom_set_default_int("mouse_move", 0); // Mouse will not move forward
|
|
|
|
// Setup resolution
|
|
doom_set_resolution(WIDTH, HEIGHT);
|
|
|
|
// Setup MIDI for songs
|
|
#if defined(WIN32)
|
|
if (midiOutGetNumDevs() != 0)
|
|
midiOutOpen(&midi_out_handle, 0, 0, 0, 0);
|
|
#elif defined(__APPLE__)
|
|
// I don't really understand this part, but it works.
|
|
AUGraph graph;
|
|
AUNode outputNode, mixerNode, dlsNode;
|
|
NewAUGraph(&graph);
|
|
AudioComponentDescription output = {'auou','ahal','appl',0,0};
|
|
AUGraphAddNode(graph, &output, &outputNode);
|
|
AUGraphOpen(graph);
|
|
AUGraphInitialize(graph);
|
|
AUGraphStart(graph);
|
|
AudioComponentDescription dls = {'aumu','dls ','appl',0,0};
|
|
AUGraphAddNode(graph, &dls, &dlsNode);
|
|
AUGraphNodeInfo(graph, dlsNode, NULL, &audio_unit);
|
|
AudioComponentDescription mixer = {'aumx','smxr','appl',0,0};
|
|
AUGraphAddNode(graph, &mixer, &mixerNode);
|
|
AUGraphConnectNodeInput(graph,mixerNode,0,outputNode,0);
|
|
AUGraphConnectNodeInput(graph,dlsNode,0,mixerNode,0);
|
|
AUGraphUpdate(graph,NULL);
|
|
#endif
|
|
midi_timer = SDL_AddTimer(0, tick_midi, 0);
|
|
|
|
// Initialize doom
|
|
doom_init(argc, argv, DOOM_FLAG_MENU_DARKEN_BG);
|
|
|
|
//-----------------------------------------------------------------------
|
|
|
|
// Main loop
|
|
SDL_PauseAudio(0);
|
|
int done = 0;
|
|
int active_mouse = 1; // Dev allow us to take mouse out of window
|
|
while (!done)
|
|
{
|
|
SDL_Event e;
|
|
Sint32 mouse_motion_x = 0;
|
|
Sint32 mouse_motion_y = 0;
|
|
while (SDL_PollEvent(&e))
|
|
{
|
|
switch (e.type)
|
|
{
|
|
case SDL_QUIT:
|
|
done = 1;
|
|
break;
|
|
|
|
case SDL_KEYDOWN:
|
|
if (e.key.keysym.scancode == SDL_SCANCODE_END)
|
|
{
|
|
active_mouse = !active_mouse;
|
|
SDL_SetRelativeMouseMode(active_mouse ? SDL_TRUE : SDL_FALSE);
|
|
}
|
|
|
|
if (!e.key.repeat)
|
|
doom_key_down(sdl_scancode_to_doom_key(e.key.keysym.scancode));
|
|
break;
|
|
|
|
case SDL_KEYUP:
|
|
if (!e.key.repeat)
|
|
doom_key_up(sdl_scancode_to_doom_key(e.key.keysym.scancode));
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
if (active_mouse) doom_button_down(sdl_button_to_doom_button(e.button.button));
|
|
break;
|
|
|
|
case SDL_MOUSEBUTTONUP:
|
|
if (active_mouse) doom_button_up(sdl_button_to_doom_button(e.button.button));
|
|
break;
|
|
|
|
case SDL_MOUSEMOTION:
|
|
if (active_mouse)
|
|
{
|
|
mouse_motion_x += e.motion.xrel;
|
|
mouse_motion_y += e.motion.yrel;
|
|
}
|
|
break;
|
|
}
|
|
if (done) break;
|
|
}
|
|
if (done) break;
|
|
|
|
if (mouse_motion_x || mouse_motion_y)
|
|
doom_mouse_move(mouse_motion_x * 4, mouse_motion_y * 4);
|
|
|
|
SDL_LockAudio();
|
|
doom_update();
|
|
SDL_UnlockAudio();
|
|
|
|
// Blit DOOM's framebuffer onto our SDL texture
|
|
#if GAME_BOY
|
|
void* dst;
|
|
const unsigned char* src = doom_get_framebuffer(3);
|
|
int src_pitch = WIDTH * 3;
|
|
int dst_pitch;
|
|
const int palette_size = 4;
|
|
const int pico8_palette[4 * 3] = {
|
|
0x0f, 0x3, 0x80f,
|
|
0x30, 0x62, 0x30,
|
|
0x8b, 0xac, 0x0f,
|
|
0x9b, 0xbc, 0x0f
|
|
};
|
|
if (!SDL_LockTexture(render_target, NULL, &dst, &dst_pitch))
|
|
{
|
|
unsigned char* dst8 = (unsigned char*)dst;
|
|
for (int y = 0; y < HEIGHT; ++y)
|
|
{
|
|
for (int x = 0; x < WIDTH; ++x)
|
|
{
|
|
int dstk = y * dst_pitch + x * 4;
|
|
int srck = y * src_pitch + x * 3;
|
|
|
|
int r = src[srck + 0];
|
|
int g = src[srck + 1];
|
|
int b = src[srck + 2];
|
|
|
|
int best = 0;
|
|
int best_score = (r * r + g * g + b * b) * 2;
|
|
for (int p = 0; p < palette_size * 3; p += 3)
|
|
{
|
|
const int* pico8_color = pico8_palette + p;
|
|
if (pico8_color[0] == r &&
|
|
pico8_color[1] == g &&
|
|
pico8_color[2] == b)
|
|
{
|
|
// Perfect match
|
|
best = p;
|
|
break;
|
|
}
|
|
int ri = pico8_color[0] - r;
|
|
int gi = pico8_color[1] - g;
|
|
int bi = pico8_color[2] - b;
|
|
int score = ri * ri + gi * gi + bi * bi;
|
|
if (score < best_score)
|
|
{
|
|
best_score = score;
|
|
best = p;
|
|
}
|
|
}
|
|
|
|
dst8[dstk + 0] = pico8_palette[best + 0];
|
|
dst8[dstk + 1] = pico8_palette[best + 1];
|
|
dst8[dstk + 2] = pico8_palette[best + 2];
|
|
dst8[dstk + 3] = 255;
|
|
}
|
|
}
|
|
SDL_UnlockTexture(render_target);
|
|
}
|
|
#elif PICO8 || CGA
|
|
void* dst;
|
|
const unsigned char* src = doom_get_framebuffer(3);
|
|
int src_pitch = WIDTH * 3;
|
|
int dst_pitch;
|
|
#if CGA
|
|
const int palette_size = 16;
|
|
const int pico8_palette[16 * 3] = {
|
|
0, 0, 0,
|
|
0, 0, 0xAA,
|
|
0, 0xAA, 0,
|
|
0, 0xAA, 0xAA,
|
|
0xAA, 0, 0,
|
|
0xAA, 0, 0xAA,
|
|
0xAA, 0x55, 0,
|
|
0xAA, 0xAA, 0xAA,
|
|
0x55, 0x55, 0x55,
|
|
0x55, 0x55, 0xFF,
|
|
0x55, 0xFF, 0x55,
|
|
0x55, 0xFF, 0xFF,
|
|
0xFF, 0x55, 0x55,
|
|
0xFF, 0x55, 0xFF,
|
|
0xFF, 0xFF, 0x55,
|
|
0xFF, 0xFF, 0xFF
|
|
};
|
|
#elif PICO8
|
|
const int palette_size = 16;
|
|
const int pico8_palette[16 * 3] = {
|
|
0, 0, 0,
|
|
29, 43, 83,
|
|
126, 37, 83,
|
|
0, 135, 81,
|
|
171, 82, 54,
|
|
95, 87, 79,
|
|
194, 195, 199,
|
|
255, 241, 232,
|
|
255, 0, 77,
|
|
255, 163, 0,
|
|
255, 236, 39,
|
|
0, 228, 54,
|
|
41, 173, 255,
|
|
131, 118, 156,
|
|
255, 119, 168,
|
|
255, 204, 170
|
|
};
|
|
#endif
|
|
if (!SDL_LockTexture(render_target, NULL, &dst, &dst_pitch))
|
|
{
|
|
unsigned char* dst8 = (unsigned char*)dst;
|
|
for (int y = 0; y < HEIGHT; ++y)
|
|
{
|
|
for (int x = 0; x < WIDTH; ++x)
|
|
{
|
|
int dstk = y * dst_pitch + x * 4;
|
|
int srck = y * src_pitch + x * 3;
|
|
|
|
int r = src[srck + 0];
|
|
int g = src[srck + 1];
|
|
int b = src[srck + 2];
|
|
|
|
int best = 0;
|
|
int best_score = (r * r + g * g + b * b) * 2;
|
|
for (int p = 0; p < palette_size * 3; p += 3)
|
|
{
|
|
const int* pico8_color = pico8_palette + p;
|
|
if (pico8_color[0] == r &&
|
|
pico8_color[1] == g &&
|
|
pico8_color[2] == b)
|
|
{
|
|
// Perfect match
|
|
best = p;
|
|
break;
|
|
}
|
|
int ri = pico8_color[0] - r;
|
|
int gi = pico8_color[1] - g;
|
|
int bi = pico8_color[2] - b;
|
|
int score = ri * ri + gi * gi + bi * bi;
|
|
if (score < best_score)
|
|
{
|
|
best_score = score;
|
|
best = p;
|
|
}
|
|
}
|
|
|
|
dst8[dstk + 0] = pico8_palette[best + 0];
|
|
dst8[dstk + 1] = pico8_palette[best + 1];
|
|
dst8[dstk + 2] = pico8_palette[best + 2];
|
|
dst8[dstk + 3] = 255;
|
|
}
|
|
}
|
|
SDL_UnlockTexture(render_target);
|
|
}
|
|
#else
|
|
void* dst;
|
|
const unsigned char* src = doom_get_framebuffer(4);
|
|
int src_pitch = WIDTH * 4;
|
|
int dst_pitch;
|
|
if (!SDL_LockTexture(render_target, NULL, &dst, &dst_pitch))
|
|
{
|
|
for (int y = 0; y < HEIGHT; ++y)
|
|
{
|
|
memcpy(dst, src, src_pitch);
|
|
dst = (unsigned char*)dst + dst_pitch;
|
|
src += src_pitch;
|
|
}
|
|
SDL_UnlockTexture(render_target);
|
|
}
|
|
#endif
|
|
|
|
// Stretch our texture on the screen
|
|
SDL_Rect src_rect = {0, 0, WIDTH, HEIGHT };
|
|
SDL_Rect dst_rect = {0, 0, FULL_WIDTH, FULL_HEIGHT};
|
|
SDL_RenderCopy(renderer, render_target, &src_rect, &dst_rect);
|
|
|
|
// Swap
|
|
SDL_RenderPresent(renderer);
|
|
}
|
|
|
|
// Shutdown
|
|
if (midi_timer) SDL_RemoveTimer(midi_timer);
|
|
#if defined(WIN32)
|
|
if (midi_out_handle) midiOutClose(midi_out_handle);
|
|
#endif
|
|
SDL_SetRelativeMouseMode(SDL_FALSE);
|
|
SDL_CloseAudio();
|
|
SDL_DestroyTexture(render_target);
|
|
SDL_DestroyRenderer(renderer);
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
}
|