Revision | 91b56854e368d087ef743bd9855d03e91e2cbdc9 (tree) |
---|---|
Time | 2020-01-18 10:20:21 |
Author | David Ludwig <dludwig@pobo...> |
Commiter | David Ludwig |
Emscripten port's initial work-in-progress
@@ -1,15 +1,19 @@ | ||
1 | 1 | # BUILD SETTINGS ############################################################### |
2 | 2 | |
3 | -ifneq ($(filter Msys Cygwin, $(shell uname -o)), ) | |
3 | +ifeq ($(patsubst %emcc,,$(lastword $(CC))),) | |
4 | + PLATFORM := EMSCRIPTEN | |
5 | + TYRIAN_DIR = /tyrian21 | |
6 | + WITH_NETWORK := false | |
7 | +else ifneq ($(filter Msys Cygwin, $(shell uname -o)), ) | |
4 | 8 | PLATFORM := WIN32 |
5 | 9 | TYRIAN_DIR = C:\\TYRIAN |
10 | + WITH_NETWORK := true | |
6 | 11 | else |
7 | 12 | PLATFORM := UNIX |
8 | 13 | TYRIAN_DIR = $(gamesdir)/tyrian |
14 | + WITH_NETWORK := true | |
9 | 15 | endif |
10 | 16 | |
11 | -WITH_NETWORK := true | |
12 | - | |
13 | 17 | ################################################################################ |
14 | 18 | |
15 | 19 | # see https://www.gnu.org/prep/standards/html_node/Makefile-Conventions.html |
@@ -40,7 +44,16 @@ | ||
40 | 44 | |
41 | 45 | ### |
42 | 46 | |
43 | -TARGET := opentyrian | |
47 | +ifeq ($(PLATFORM), EMSCRIPTEN) | |
48 | + TARGET := opentyrian.js | |
49 | +else | |
50 | + TARGET := opentyrian | |
51 | +endif | |
52 | + | |
53 | +ALL_OUTPUT_BINARIES := $(TARGET) | |
54 | +ifeq ($(PLATFORM), EMSCRIPTEN) | |
55 | + ALL_OUTPUT_BINARIES := $(ALL_OUTPUT_BINARIES) opentyrian.wasm opentyrian.wasm.map | |
56 | +endif | |
44 | 57 | |
45 | 58 | SRCS := $(wildcard src/*.c) |
46 | 59 | OBJS := $(SRCS:src/%.c=obj/%.o) |
@@ -63,11 +76,20 @@ | ||
63 | 76 | CFLAGS += -MMD |
64 | 77 | CFLAGS += -Wall \ |
65 | 78 | -Wextra \ |
66 | - -Wno-missing-field-initializers | |
79 | + -Wno-absolute-value \ | |
80 | + -Wno-missing-field-initializers \ | |
81 | + -Wno-unused-const-variable | |
67 | 82 | CFLAGS += -O2 |
68 | 83 | LDFLAGS := |
69 | 84 | LDLIBS := |
70 | 85 | |
86 | +ifeq ($(PLATFORM), EMSCRIPTEN) | |
87 | + EMSCRIPTEN_CFLAGS := -s ALLOW_MEMORY_GROWTH=1 -s ASYNCIFY | |
88 | + CPPFLAGS += $(EMSCRIPTEN_CFLAGS) | |
89 | + CFLAGS += $(EMSCRIPTEN_CFLAGS) | |
90 | + LDFLAGS += --preload-file tyrian21/ | |
91 | +endif | |
92 | + | |
71 | 93 | SDL_CPPFLAGS := $(shell $(PKG_CONFIG) sdl2 --cflags) |
72 | 94 | SDL_LDFLAGS := $(shell $(PKG_CONFIG) sdl2 --libs-only-L --libs-only-other) |
73 | 95 | SDL_LDLIBS := $(shell $(PKG_CONFIG) sdl2 --libs-only-l) |
@@ -96,8 +118,10 @@ | ||
96 | 118 | .PHONY : debug |
97 | 119 | debug : CPPFLAGS += -UNDEBUG |
98 | 120 | debug : CFLAGS += -Werror |
99 | -debug : CFLAGS += -O0 | |
100 | -debug : CFLAGS += -g3 | |
121 | +# debug : CFLAGS += -O0 | |
122 | +debug : CFLAGS += $(if $(filter EMSCRIPTEN,$(PLATFORM)),-O1,-O0) | |
123 | +debug : CFLAGS += $(if $(filter EMSCRIPTEN,$(PLATFORM)),--source-map-base http://localhost:8080/opentyrian/,) | |
124 | +debug : CFLAGS += $(if $(filter EMSCRIPTEN,$(PLATFORM)),-g4,-g3) | |
101 | 125 | debug : all |
102 | 126 | |
103 | 127 | .PHONY : installdirs |
@@ -122,7 +146,7 @@ | ||
122 | 146 | clean : |
123 | 147 | rm -f $(OBJS) |
124 | 148 | rm -f $(DEPS) |
125 | - rm -f $(TARGET) | |
149 | + rm -f $(ALL_OUTPUT_BINARIES) | |
126 | 150 | |
127 | 151 | $(TARGET) : $(OBJS) |
128 | 152 | $(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -o $@ $^ $(ALL_LDLIBS) |
@@ -0,0 +1,179 @@ | ||
1 | + | |
2 | +:root { | |
3 | + --canvas-scaling-factor: 1.0; | |
4 | +} | |
5 | + | |
6 | +body { | |
7 | + background-color: black; | |
8 | +} | |
9 | + | |
10 | +.circle { | |
11 | + position: absolute; | |
12 | + width: 100px; | |
13 | + height: 100px; | |
14 | + border-radius: 50%; | |
15 | + background-color: #dddddd; | |
16 | + -webkit-transform: scale(1, 1); | |
17 | + transform: scale(1, 1); | |
18 | +} | |
19 | + | |
20 | +.circle.circle_pulse { | |
21 | + -webkit-animation-timing-function: ease; | |
22 | + animation-timing-function: ease; | |
23 | + -webkit-animation: circle_pulse 2s infinite; | |
24 | + animation: circle_pulse 2s infinite; | |
25 | + background-color: #ffffff; | |
26 | +} | |
27 | + | |
28 | +.circle_svg { | |
29 | + fill: #333333; | |
30 | + stroke: #333333; | |
31 | + stroke-linejoin: round; | |
32 | + stroke-width: 5; | |
33 | + transition: all 0.3s; | |
34 | +} | |
35 | + | |
36 | +.circle_svg:hover { | |
37 | + /* cursor: pointer; */ | |
38 | + fill: #000000; | |
39 | + stroke: #000000; | |
40 | + -webkit-transform: scale(1.2, 1.2); | |
41 | + transform: scale(1.2, 1.2); | |
42 | +} | |
43 | + | |
44 | +@-webkit-keyframes circle_pulse { | |
45 | + 0% { | |
46 | + -webkit-transform: scale(1, 1); | |
47 | + transform: scale(1, 1); | |
48 | + } | |
49 | + 25% { | |
50 | + -webkit-transform: scale(1, 1); | |
51 | + transform: scale(1, 1); | |
52 | + } | |
53 | + 50% { | |
54 | + -webkit-transform: scale(1.2, 1.2); | |
55 | + transform: scale(1.2, 1.2); | |
56 | + } | |
57 | + 100% { | |
58 | + -webkit-transform: scale(1, 1); | |
59 | + transform: scale(1, 1); | |
60 | + } | |
61 | +} | |
62 | + | |
63 | +@keyframes circle_pulse { | |
64 | + 0% { | |
65 | + -webkit-transform: scale(1, 1); | |
66 | + transform: scale(1, 1); | |
67 | + } | |
68 | + 25% { | |
69 | + -webkit-transform: scale(1, 1); | |
70 | + transform: scale(1, 1); | |
71 | + } | |
72 | + 50% { | |
73 | + -webkit-transform: scale(1.2, 1.2); | |
74 | + transform: scale(1.2, 1.2); | |
75 | + } | |
76 | + 100% { | |
77 | + -webkit-transform: scale(1, 1); | |
78 | + transform: scale(1, 1); | |
79 | + } | |
80 | +} | |
81 | + | |
82 | +.content_window { | |
83 | + position: absolute; | |
84 | + top: 50%; | |
85 | + left: 50%; | |
86 | + transform: translate(-50%, -50%) scale(var(--canvas-scaling-factor)); | |
87 | + width: 320px; | |
88 | + height: 200px; | |
89 | + /* display over the HUD */ | |
90 | + z-index: 2; | |
91 | +} | |
92 | + | |
93 | +.coverer { | |
94 | + position: absolute; | |
95 | + top: 0; | |
96 | + left: 0; | |
97 | + right: 0; | |
98 | + bottom: 0; | |
99 | + display: flex; | |
100 | + align-items: center; | |
101 | + justify-content: center; | |
102 | + cursor: pointer; | |
103 | + background-color: darkblue; | |
104 | + border-color: darkgrey; | |
105 | + border-width: 1px; | |
106 | + border-style: solid; | |
107 | +} | |
108 | + | |
109 | +canvas.emscripten { | |
110 | + image-rendering: -moz-crisp-edges; | |
111 | + image-rendering: -webkit-crisp-edges; | |
112 | + image-rendering: pixelated; | |
113 | + image-rendering: crisp-edges; | |
114 | + background-color: black; | |
115 | + /* the canvas *must not* have any border or padding, or mouse coords will be wrong */ | |
116 | + border: 0px none; | |
117 | +} | |
118 | + | |
119 | +#hud { | |
120 | + position: absolute; | |
121 | + top: 0; | |
122 | + left: 0; | |
123 | + right: 0; | |
124 | + width: 100%; | |
125 | + /* height: 100px; */ | |
126 | + z-index: 1; | |
127 | + /* background-color: green; */ | |
128 | + color: white; | |
129 | + padding: 4px; | |
130 | +} | |
131 | + | |
132 | +.hudButton { | |
133 | + /* position: absolute; */ | |
134 | + position: relative; | |
135 | + right: 8px; | |
136 | + background-color: #ffffff; | |
137 | + padding: 8px; | |
138 | + border-radius: 8px; | |
139 | + /* right: 0; */ | |
140 | + float: right; | |
141 | + /* box-shadow: 4px 4px gray; */ | |
142 | +} | |
143 | + | |
144 | +.hudIcon { | |
145 | + display: block; | |
146 | + margin: auto; | |
147 | + width: 24px; | |
148 | + height: 24px; | |
149 | + padding: 8px; | |
150 | +} | |
151 | + | |
152 | +.hudText { | |
153 | + display: block; | |
154 | + color: black; | |
155 | + font-family: Sans-serif; | |
156 | + /* text-transform: uppercase; */ | |
157 | +} | |
158 | + | |
159 | +.loader { | |
160 | + border: 16px solid #00000033; | |
161 | + border-top: 16px solid #ffffff; | |
162 | + border-radius: 50%; | |
163 | + width: 100px; | |
164 | + height: 100px; | |
165 | + animation: spin 2s linear infinite; | |
166 | +} | |
167 | + | |
168 | +@keyframes spin { | |
169 | + 0% { transform: rotate(0deg); } | |
170 | + 100% { transform: rotate(360deg); } | |
171 | +} | |
172 | + | |
173 | +#click_to_start { | |
174 | + /* visibility: visible; */ | |
175 | +} | |
176 | + | |
177 | +#waiting_to_start { | |
178 | + visibility: hidden; | |
179 | +} |
@@ -0,0 +1,150 @@ | ||
1 | +<!DOCTYPE html> | |
2 | +<html> | |
3 | + | |
4 | +<head> | |
5 | + <meta name="viewport" content="width=device-width, initial-scale=1"> | |
6 | + <!-- <script type="text/javascript" src="/live.js"></script> --> | |
7 | + <!-- <script type="text/javascript" src="http://livejs.com/live.js"></script> --> | |
8 | + <link rel="stylesheet" type="text/css" href="opentyrian.css"> | |
9 | + | |
10 | + <!-- HACK: use 'as="fetch" crossorigin', rather than just 'as="script"', | |
11 | + to suppress preload-related warnings in Chrome --> | |
12 | + <link rel="preload" href="opentyrian.js" as="fetch" crossorigin> | |
13 | + <link rel="preload" href="opentyrian.wasm" as="fetch" crossorigin> | |
14 | +</head> | |
15 | + | |
16 | +<body> | |
17 | + <div id="hud"> | |
18 | + <div class="hudButton" onclick="toggleFullscreen()"> | |
19 | + <svg class="hudIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> | |
20 | + <path d="M24 9h-2v-7h-7v-2h9v9zm-9 15v-2h7v-7h2v9h-9zm-15-9h2v7h7v2h-9v-9zm9-15v2h-7v7h-2v-9h9z"/> | |
21 | + </svg> | |
22 | + <div class="hudText">Fullscreen</div> | |
23 | + </div> | |
24 | + <!-- <span>Scaling: <span id="hudScalingFactor"></span>X</span> --> | |
25 | + <!-- <button onclick="hideCover()">Hide Cover</button> | |
26 | + <button onclick="startXHR()">Start XHR</button> | |
27 | + <button onclick="execMainCode()">Exec Main Code</button> --> | |
28 | + </div> | |
29 | + <canvas class="content_window emscripten" id="canvas" width="320" height="200" oncontextmenu="event.preventDefault()"></canvas> | |
30 | + <div id="cover" class="content_window" onclick="userStarted()"> | |
31 | + <div id="click_to_start" class="coverer"> | |
32 | + <div class="circle circle_pulse"></div> | |
33 | + <div class="circle"> | |
34 | + <svg class="circle_svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> | |
35 | + <polygon points="40,30 65,50 40,70"></polygon> | |
36 | + </svg> | |
37 | + </div> | |
38 | + </div> | |
39 | + <div id="waiting_to_start" class="coverer"> | |
40 | + <div class="loader"></div> | |
41 | + </div> | |
42 | + </div> | |
43 | + <script type="text/javascript"> | |
44 | + Module = { | |
45 | + printErr: function (text) { | |
46 | + if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); | |
47 | + console.error(text); | |
48 | + }, | |
49 | + canvas: (function () { | |
50 | + var canvas = document.getElementById('canvas'); | |
51 | + | |
52 | + // As a default initial behavior, pop up an alert when webgl context is lost. To make your | |
53 | + // application robust, you may want to override this behavior before shipping! | |
54 | + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 | |
55 | + canvas.addEventListener("webglcontextlost", function (e) { | |
56 | + alert('WebGL context lost. You will need to reload the page.'); | |
57 | + e.preventDefault(); | |
58 | + }, false); | |
59 | + | |
60 | + return canvas; | |
61 | + })(), | |
62 | + }; | |
63 | + | |
64 | + var MainCode = null; | |
65 | + var InteractionStarted = false; | |
66 | + | |
67 | + function toggleFullscreen() { | |
68 | + var elem = document.documentElement; | |
69 | + if (!document.fullscreenElement) { | |
70 | + elem.requestFullscreen().catch(err => { | |
71 | + alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`); | |
72 | + }); | |
73 | + } else { | |
74 | + document.exitFullscreen(); | |
75 | + } | |
76 | + } | |
77 | + | |
78 | + function startXHR() { | |
79 | + // console.log("XHR CREATION"); | |
80 | + var xhr = new XMLHttpRequest(); | |
81 | + xhr.open("GET", "opentyrian.js"); | |
82 | + xhr.onload = function(e) { | |
83 | + if (xhr.readyState === 4) { | |
84 | + if (xhr.status === 200) { | |
85 | + // setTimeout(function () { | |
86 | + // console.log("XHR SUCCESS"); | |
87 | + MainCode = xhr.responseText; | |
88 | + advanceUIState(); | |
89 | + // }, 4000); // ONLY USE FOR TESTING! | |
90 | + } else { | |
91 | + console.error("XHR HTTP ERROR:", xhr.statusText); | |
92 | + } | |
93 | + } | |
94 | + }; | |
95 | + xhr.onerror = function(e) { | |
96 | + console.error("XHR GENERIC ERROR:", xhr.statusText); | |
97 | + }; | |
98 | + xhr.send(null); | |
99 | + } | |
100 | + | |
101 | + function advanceUIState() { | |
102 | + if (!InteractionStarted) { | |
103 | + // keep showing '#cover' | |
104 | + } else if (MainCode == null) { | |
105 | + document.getElementById("waiting_to_start").style.setProperty("visibility", "visible"); | |
106 | + document.getElementById("click_to_start").style.setProperty("display", "none"); | |
107 | + // keep showing '#waiting_to_start' | |
108 | + } else { | |
109 | + // hide the cover | |
110 | + document.getElementById("cover").style.setProperty("display", "none"); | |
111 | + execMainCode(); | |
112 | + } | |
113 | + } | |
114 | + | |
115 | + function userStarted() { | |
116 | + InteractionStarted = true; | |
117 | + advanceUIState(); | |
118 | + } | |
119 | + | |
120 | + function execMainCode() { | |
121 | + if (MainCode == null) { | |
122 | + console.error("Cannot execute MainCode, which has not been fully-loaded") | |
123 | + return; | |
124 | + } | |
125 | + const execMainCode = new Function('Module', MainCode); | |
126 | + execMainCode(Module); | |
127 | + } | |
128 | + | |
129 | + function handleResize() { | |
130 | + var canvas = document.getElementById("canvas"); | |
131 | + const scaleMaxX = Math.floor(window.innerWidth / canvas.width); | |
132 | + const scaleMaxY = Math.floor(window.innerHeight / canvas.height); | |
133 | + const scale = Math.max(1, Math.min(scaleMaxX, scaleMaxY)); | |
134 | + // console.log("handleResize", scale, "<--", scaleMaxX, scaleMaxY); | |
135 | + document.documentElement.style.setProperty("--canvas-scaling-factor", scale); | |
136 | + var hudScalingFactor = document.getElementById("hudScalingFactor"); | |
137 | + if (hudScalingFactor) { | |
138 | + hudScalingFactor.textContent = scale; | |
139 | + } | |
140 | + } | |
141 | + | |
142 | + window.addEventListener('load', handleResize); | |
143 | + window.addEventListener('resize', handleResize); | |
144 | + window.addEventListener("load", startXHR); | |
145 | + | |
146 | + // drawDebugCanvas(); | |
147 | + </script> | |
148 | +</body> | |
149 | + | |
150 | +</html> | |
\ No newline at end of file |
@@ -222,8 +222,14 @@ | ||
222 | 222 | { |
223 | 223 | // defaults |
224 | 224 | fullscreen_display = -1; |
225 | + | |
226 | +#if __EMSCRIPTEN__ | |
227 | + // The Emscripten port leaves scaling to HTML+CSS. | |
228 | + set_scaler_by_name("None"); | |
229 | +#else | |
225 | 230 | set_scaler_by_name("Scale2x"); |
226 | - | |
231 | +#endif | |
232 | + | |
227 | 233 | Config *config = &opentyrian_config; |
228 | 234 | |
229 | 235 | FILE *file = dir_fopen_warn(get_user_directory(), "opentyrian.cfg", "r"); |
@@ -64,6 +64,7 @@ | ||
64 | 64 | #include "video.h" |
65 | 65 | |
66 | 66 | #include <assert.h> |
67 | +#include <SDL.h> | |
67 | 68 | |
68 | 69 | /*** Defines ***/ |
69 | 70 | #define UNIT_HEIGHT 12 |
@@ -26,6 +26,10 @@ | ||
26 | 26 | #include <SDL.h> |
27 | 27 | #include <stdio.h> |
28 | 28 | |
29 | +#if __EMSCRIPTEN__ | |
30 | +#include <emscripten.h> | |
31 | +#endif | |
32 | + | |
29 | 33 | JE_boolean ESCPressed; |
30 | 34 | |
31 | 35 | JE_boolean newkey, newmouse, keydown, mousedown; |
@@ -130,6 +134,15 @@ | ||
130 | 134 | void service_SDL_events( JE_boolean clear_new ) |
131 | 135 | { |
132 | 136 | SDL_Event ev; |
137 | + | |
138 | +#if __EMSCRIPTEN__ | |
139 | + // Yield time back to the web-browser, via use of Emscripten's | |
140 | + // Asyncify feature (https://emscripten.org/docs/porting/asyncify.html). | |
141 | + // This allows events to be collected without needing to overhaul | |
142 | + // OpenTyrian to run in a web-browser's callback-based system of | |
143 | + // events and rendering. | |
144 | + emscripten_sleep(0); | |
145 | +#endif | |
133 | 146 | |
134 | 147 | if (clear_new) |
135 | 148 | { |
@@ -146,6 +146,30 @@ | ||
146 | 146 | }; |
147 | 147 | |
148 | 148 | |
149 | +// per-chip variables | |
150 | +Bitu chip_num; | |
151 | +op_type op[MAXOPERATORS]; | |
152 | + | |
153 | +Bits int_samplerate; | |
154 | + | |
155 | +Bit8u status; | |
156 | +Bit32u opl_index; | |
157 | +#if defined(OPLTYPE_IS_OPL3) | |
158 | +Bit8u adlibreg[512]; // adlib register set (including second set) | |
159 | +Bit8u wave_sel[44]; // waveform selection | |
160 | +#else | |
161 | +Bit8u adlibreg[256]; // adlib register set | |
162 | +Bit8u wave_sel[22]; // waveform selection | |
163 | +#endif | |
164 | + | |
165 | + | |
166 | +// vibrato/tremolo increment/counter | |
167 | +Bit32u vibtab_pos; | |
168 | +Bit32u vibtab_add; | |
169 | +Bit32u tremtab_pos; | |
170 | +Bit32u tremtab_add; | |
171 | + | |
172 | + | |
149 | 173 | void operator_advance(op_type* op_pt, Bit32s vib) { |
150 | 174 | op_pt->wfpos = op_pt->tcount; // waveform position |
151 | 175 |
@@ -148,6 +148,7 @@ | ||
148 | 148 | #endif |
149 | 149 | } op_type; |
150 | 150 | |
151 | +/* | |
151 | 152 | // per-chip variables |
152 | 153 | Bitu chip_num; |
153 | 154 | op_type op[MAXOPERATORS]; |
@@ -170,7 +171,7 @@ | ||
170 | 171 | Bit32u vibtab_add; |
171 | 172 | Bit32u tremtab_pos; |
172 | 173 | Bit32u tremtab_add; |
173 | - | |
174 | +*/ | |
174 | 175 | |
175 | 176 | // enable an operator |
176 | 177 | void enable_operator(Bitu regbase, op_type* op_pt, Bit32u act_type); |
@@ -62,6 +62,8 @@ | ||
62 | 62 | |
63 | 63 | void init_video( void ) |
64 | 64 | { |
65 | + Uint32 main_window_flags; | |
66 | + | |
65 | 67 | if (SDL_WasInit(SDL_INIT_VIDEO)) |
66 | 68 | return; |
67 | 69 |
@@ -87,9 +89,15 @@ | ||
87 | 89 | |
88 | 90 | // Create the window with a temporary initial size, hidden until we set up the |
89 | 91 | // scaler and find the true window size |
92 | + main_window_flags = SDL_WINDOW_HIDDEN; | |
93 | +#ifndef __EMSCRIPTEN__ | |
94 | + // The Emscripten port can handle resizing via HTML+CSS. Don't enable | |
95 | + // resizable windows for that port, but do enable it for other platforms. | |
96 | + main_window_flags |= SDL_WINDOW_RESIZABLE; | |
97 | +#endif | |
90 | 98 | main_window = SDL_CreateWindow("OpenTyrian", |
91 | 99 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, |
92 | - vga_width, vga_height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN); | |
100 | + vga_width, vga_height, main_window_flags); | |
93 | 101 | |
94 | 102 | if (main_window == NULL) |
95 | 103 | { |