Skip to content

Commit 2e5e311

Browse files
committed
emscripten compatibility
1 parent 1f63da6 commit 2e5e311

File tree

5 files changed

+315
-8
lines changed

5 files changed

+315
-8
lines changed

cmake/PortCEWeb.cmake

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
function(portce_add_web_shell target)
2+
if(NOT TARGET "${target}")
3+
message(FATAL_ERROR "portce_add_web_shell: target '${target}' does not exist.")
4+
endif()
5+
6+
set(options)
7+
set(one_value_args OUT_DIR TEMPLATE TITLE)
8+
set(multi_value_args)
9+
cmake_parse_arguments(PORTCE_WEB "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
10+
11+
if(NOT PORTCE_WEB_OUT_DIR)
12+
message(FATAL_ERROR "portce_add_web_shell: OUT_DIR is required.")
13+
endif()
14+
15+
if(NOT PORTCE_WEB_TEMPLATE)
16+
message(FATAL_ERROR "portce_add_web_shell: TEMPLATE is required.")
17+
endif()
18+
19+
get_filename_component(PORTCE_WEB_OUT_DIR_ABS "${PORTCE_WEB_OUT_DIR}" ABSOLUTE)
20+
file(MAKE_DIRECTORY "${PORTCE_WEB_OUT_DIR_ABS}")
21+
22+
set(PORTCE_APP_JS "${target}.js")
23+
if(PORTCE_WEB_TITLE)
24+
set(PORTCE_WEB_TITLE_TEXT "${PORTCE_WEB_TITLE}")
25+
else()
26+
set(PORTCE_WEB_TITLE_TEXT "PortCE Web")
27+
endif()
28+
29+
configure_file(
30+
"${PORTCE_WEB_TEMPLATE}"
31+
"${PORTCE_WEB_OUT_DIR_ABS}/${target}.html"
32+
@ONLY
33+
)
34+
endfunction()

examples/hello_world/CMakeLists.txt

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ set(PROJECT_NAME "hello_world")
66
project(${PROJECT_NAME})
77

88
set(SRC_DIR "./src")
9+
set(PORTCE_SOURCE_ROOT "${CMAKE_CURRENT_LIST_DIR}/../..")
910

1011
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "./bin")
1112

1213
# Compiler
13-
set(CMAKE_C_COMPILER clang)
14-
set(CMAKE_CXX_COMPILER clang++)
14+
if(NOT EMSCRIPTEN)
15+
set(CMAKE_C_COMPILER clang)
16+
set(CMAKE_CXX_COMPILER clang++)
17+
endif()
1518

1619
# Set C and C++ standards
1720
set(CMAKE_C_STANDARD 17)
@@ -22,7 +25,13 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
2225
set(CMAKE_COLOR_DIAGNOSTICS ON)
2326

2427
# Packages
25-
find_package(PortCE REQUIRED)
28+
find_package(PortCE CONFIG QUIET)
29+
if(NOT PortCE_FOUND)
30+
add_subdirectory("${PORTCE_SOURCE_ROOT}" "${CMAKE_BINARY_DIR}/portce" EXCLUDE_FROM_ALL)
31+
endif()
32+
if(NOT PortCE_FOUND AND NOT TARGET PortCE::PortCE)
33+
message(FATAL_ERROR "PortCE not found. Install it or build from repo root so PortCE::PortCE is available.")
34+
endif()
2635

2736
file(GLOB_RECURSE SRC_FILES "${SRC_DIR}/*.c" "${SRC_DIR}/*.cpp")
2837

@@ -66,3 +75,19 @@ target_compile_options(
6675
)
6776

6877
target_link_libraries(${PROJECT_NAME} PUBLIC PortCE::PortCE)
78+
79+
if(EMSCRIPTEN)
80+
include("${PORTCE_SOURCE_ROOT}/cmake/PortCEWeb.cmake")
81+
get_filename_component(
82+
PORTCE_WEB_OUT_DIR
83+
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
84+
ABSOLUTE
85+
BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}"
86+
)
87+
portce_add_web_shell(
88+
${PROJECT_NAME}
89+
OUT_DIR "${PORTCE_WEB_OUT_DIR}"
90+
TEMPLATE "${PORTCE_SOURCE_ROOT}/web/portce_shell.html.in"
91+
TITLE "PortCE Hello World"
92+
)
93+
endif()

src/CMakeLists.txt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ set(${LIBRARY_TARGET_NAME}_SRC ${SRC_FILES} )
3030
set(${LIBRARY_TARGET_NAME}_HDR ${HDR_FILES})
3131

3232
# Compiler
33-
set(CMAKE_C_COMPILER clang)
34-
set(CMAKE_CXX_COMPILER clang++)
33+
if(NOT EMSCRIPTEN)
34+
set(CMAKE_C_COMPILER clang)
35+
set(CMAKE_CXX_COMPILER clang++)
36+
endif()
3537

3638
# Set C and C++ standards
3739
set(CMAKE_C_STANDARD 17)
@@ -58,8 +60,10 @@ set(CMAKE_COLOR_DIAGNOSTICS ON)
5860
# find_package(FooPackage) may import either
5961
# - variables: FooPackage_INCLUDE_DIRS and FooPackage_LIBRARIES
6062
# - target: FooPackage::FooPackage
61-
find_package(SDL2 REQUIRED)
62-
find_package(SDL2_mixer REQUIRED)
63+
if(NOT EMSCRIPTEN)
64+
find_package(SDL2 REQUIRED)
65+
find_package(SDL2_mixer REQUIRED)
66+
endif()
6367

6468
# Adds a library target called ${LIBRARY_TARGET_NAME} to be built from the
6569
# source and header files listed in the command invocation.
@@ -171,7 +175,13 @@ target_include_directories(
171175
# target_link_libraries(${LIBRARY_TARGET_NAME} ${FooPackage_LIBRARIES})
172176
# - with imported target:
173177
# target_link_libraries(${LIBRARY_TARGET_NAME} FooPackage_LIBRARIES::FooPackage_LIBRARIES)
174-
target_link_libraries(${PROJECT_NAME} PUBLIC SDL2::SDL2 SDL2_mixer::SDL2_mixer -lm)
178+
if(EMSCRIPTEN)
179+
target_compile_options(${PROJECT_NAME} PUBLIC "SHELL:-sUSE_SDL=2" "SHELL:-sUSE_SDL_MIXER=2")
180+
target_link_options(${PROJECT_NAME} PUBLIC "SHELL:-sUSE_SDL=2" "SHELL:-sUSE_SDL_MIXER=2" "SHELL:-sASYNCIFY")
181+
else()
182+
target_link_libraries(${PROJECT_NAME} PUBLIC SDL2::SDL2 SDL2_mixer::SDL2_mixer)
183+
endif()
184+
target_link_libraries(${PROJECT_NAME} PUBLIC m)
175185

176186
# Specify installation targets, typology and destination folders.
177187
install(

src/PortCE_Render.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323

2424
#define SDL_MAIN_HANDLED
2525
#include <SDL.h>
26+
#ifdef __EMSCRIPTEN__
27+
#include <emscripten/emscripten.h>
28+
#endif
2629

2730
#include <pthread.h>
2831
#include <sched.h>
@@ -234,13 +237,24 @@ static uint8_t internal_CSC_Scan(void) {
234237

235238
uint8_t os_GetCSC(void) {
236239
static nano64_t prev_time = 0;
240+
#ifdef __EMSCRIPTEN__
241+
const double frame_ms = 1000.0 / 60.0;
242+
double current_ms = emscripten_get_now();
243+
if (current_ms - (double)prev_time < frame_ms) {
244+
emscripten_sleep(0);
245+
return 0;
246+
}
247+
prev_time = (nano64_t)current_ms;
248+
PortCE_update_registers();
249+
#else
237250
nano64_t current_time;
238251
do {
239252
current_time = getNanoTime();
240253
// or some other busy wait function idk
241254
PortCE_update_registers();
242255
} while (current_time - prev_time < FRAMERATE_TO_NANO(60.0));
243256
prev_time = current_time;
257+
#endif
244258

245259
uint8_t key = internal_CSC_Scan();
246260
PortCE_new_frame();
@@ -361,6 +375,11 @@ uint16_t os_GetKey(void) {
361375
while (key == KB_None) {
362376
key = internal_CSC_Scan();
363377
PortCE_new_frame();
378+
#ifdef __EMSCRIPTEN__
379+
if (key == KB_None) {
380+
emscripten_sleep(0);
381+
}
382+
#endif
364383
}
365384

366385
os_KbdGetKy = internal_KdbGetKy(key);

web/portce_shell.html.in

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>@PORTCE_WEB_TITLE_TEXT@</title>
7+
<style>
8+
:root {
9+
--bg: #121212;
10+
--panel: #1d1d1d;
11+
--accent: #f2c879;
12+
--text: #e6e6e6;
13+
}
14+
html, body {
15+
margin: 0;
16+
height: 100%;
17+
background: radial-gradient(circle at 20% 0%, #1f1c16 0%, #121212 50%);
18+
color: var(--text);
19+
font-family: "Georgia", "Times New Roman", serif;
20+
}
21+
.wrap {
22+
display: grid;
23+
grid-template-rows: auto 1fr;
24+
height: 100%;
25+
}
26+
header {
27+
padding: 10px 14px;
28+
background: linear-gradient(135deg, #1b1b1b, #2a2a2a);
29+
border-bottom: 1px solid #2f2f2f;
30+
letter-spacing: 0.5px;
31+
}
32+
header span {
33+
font-size: 14px;
34+
opacity: 0.8;
35+
margin-left: 6px;
36+
}
37+
.stage {
38+
display: grid;
39+
place-items: center;
40+
position: relative;
41+
padding: 16px;
42+
}
43+
.panel {
44+
background: var(--panel);
45+
border: 1px solid #2b2b2b;
46+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5);
47+
padding: 16px;
48+
border-radius: 10px;
49+
}
50+
.toolbar {
51+
display: grid;
52+
grid-template-columns: 1fr auto;
53+
align-items: center;
54+
margin-bottom: 10px;
55+
}
56+
.toolbar .scales {
57+
display: flex;
58+
gap: 8px;
59+
align-items: center;
60+
}
61+
.toolbar button {
62+
background: #2a2a2a;
63+
border: 1px solid #3b3b3b;
64+
color: var(--text);
65+
padding: 6px 10px;
66+
border-radius: 6px;
67+
cursor: pointer;
68+
font-size: 12px;
69+
}
70+
.toolbar button.active {
71+
border-color: var(--accent);
72+
color: var(--accent);
73+
}
74+
.screen {
75+
display: grid;
76+
place-items: center;
77+
background: #0b0b0b;
78+
border: 1px solid #333;
79+
border-radius: 8px;
80+
padding: 10px;
81+
}
82+
#canvas {
83+
width: 320px;
84+
height: 240px;
85+
image-rendering: pixelated;
86+
image-rendering: crisp-edges;
87+
background: #000;
88+
display: block;
89+
}
90+
.hint {
91+
position: absolute;
92+
top: 10px;
93+
right: 12px;
94+
font-size: 12px;
95+
opacity: 0.6;
96+
}
97+
.note {
98+
font-size: 12px;
99+
opacity: 0.7;
100+
margin-top: 10px;
101+
}
102+
@media (max-width: 640px) {
103+
.panel {
104+
width: 100%;
105+
box-sizing: border-box;
106+
}
107+
#canvas {
108+
width: 100%;
109+
height: auto;
110+
aspect-ratio: 4 / 3;
111+
}
112+
}
113+
</style>
114+
</head>
115+
<body>
116+
<div class="wrap">
117+
<header>
118+
@PORTCE_WEB_TITLE_TEXT@
119+
</header>
120+
<div class="stage">
121+
<div class="panel">
122+
<div class="toolbar">
123+
<div class="scales">
124+
<button type="button" data-scale="1">1x</button>
125+
<button type="button" data-scale="2" class="active">2x</button>
126+
<button type="button" data-scale="3">3x</button>
127+
<button type="button" id="fullscreen-btn">Full-screen</button>
128+
</div>
129+
<button type="button" id="reset-btn">Reset</button>
130+
</div>
131+
<div class="screen">
132+
<canvas id="canvas" width="320" height="240"></canvas>
133+
</div>
134+
<div class="note">Click the canvas to focus input.</div>
135+
</div>
136+
<div class="hint">PortCE Web</div>
137+
</div>
138+
</div>
139+
<script>
140+
(function () {
141+
const baseW = 320;
142+
const baseH = 240;
143+
const canvas = document.getElementById("canvas");
144+
const buttons = document.querySelectorAll("[data-scale]");
145+
const resetBtn = document.getElementById("reset-btn");
146+
const fullscreenBtn = document.getElementById("fullscreen-btn");
147+
const panel = document.querySelector(".panel");
148+
const screen = document.querySelector(".screen");
149+
let fullscreenActive = false;
150+
151+
function setScale(scale) {
152+
fullscreenActive = false;
153+
canvas.style.width = (baseW * scale) + "px";
154+
canvas.style.height = (baseH * scale) + "px";
155+
buttons.forEach(function (btn) {
156+
btn.classList.toggle("active", btn.dataset.scale === String(scale));
157+
});
158+
fullscreenBtn.classList.remove("active");
159+
canvas.style.maxWidth = "";
160+
canvas.style.maxHeight = "";
161+
screen.style.width = "";
162+
screen.style.height = "";
163+
}
164+
function fitCanvasToScreen() {
165+
const screenRect = screen.getBoundingClientRect();
166+
const maxW = screenRect.width - 20;
167+
const maxH = screenRect.height - 20;
168+
let scale = Math.floor(Math.min(maxW / baseW, maxH / baseH) * 100) / 100;
169+
scale = Math.max(scale, 1);
170+
canvas.style.width = (baseW * scale) + "px";
171+
canvas.style.height = (baseH * scale) + "px";
172+
}
173+
buttons.forEach(function (btn) {
174+
btn.addEventListener("click", function () {
175+
setScale(Number(btn.dataset.scale));
176+
});
177+
});
178+
resetBtn.addEventListener("click", function () {
179+
location.reload();
180+
});
181+
fullscreenBtn.addEventListener("click", function () {
182+
if (!document.fullscreenElement) {
183+
(panel.requestFullscreen || panel.webkitRequestFullscreen || panel.msRequestFullscreen).call(panel);
184+
} else {
185+
(document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen).call(document);
186+
}
187+
});
188+
document.addEventListener("fullscreenchange", function () {
189+
fullscreenActive = !!document.fullscreenElement;
190+
fullscreenBtn.classList.toggle("active", fullscreenActive);
191+
if (fullscreenActive) {
192+
screen.style.width = "100%";
193+
screen.style.height = "calc(100vh - 120px)";
194+
fitCanvasToScreen();
195+
} else {
196+
screen.style.width = "";
197+
screen.style.height = "";
198+
setScale(2);
199+
}
200+
});
201+
window.addEventListener("resize", function () {
202+
if (fullscreenActive) {
203+
fitCanvasToScreen();
204+
}
205+
});
206+
setScale(2);
207+
})();
208+
var Module = {
209+
canvas: (function () {
210+
const canvas = document.getElementById("canvas");
211+
canvas.tabIndex = 0;
212+
canvas.focus();
213+
return canvas;
214+
})()
215+
};
216+
</script>
217+
<script src="@PORTCE_APP_JS@"></script>
218+
</body>
219+
</html>

0 commit comments

Comments
 (0)