Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions emscripten/pages-index.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
/* The old direct launch and the ZIP path stay wired for the script but out of sight. */
#launch,#reload,label[for=ownershipFiles],#clearOwnership{display:none}

.quality{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin-top:14px;padding:4px;border-radius:14px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08)}
.quality button{padding:9px 6px;border-radius:11px;font-size:14px;font-weight:600;color:var(--ink);background:transparent;box-shadow:none}
.quality button[aria-pressed=true]{color:#06121b;background:linear-gradient(180deg,#6fd0ff,var(--blue));box-shadow:0 4px 14px rgba(47,180,255,.3)}
#qualityHint{margin-top:6px;font-size:12.5px;color:var(--dim)}
details{width:100%;color:var(--dim);font-size:13px}
summary{list-style:none;cursor:pointer;text-align:center;padding:6px;font-size:13px;color:var(--dim)}
summary::-webkit-details-marker{display:none}
Expand Down Expand Up @@ -103,6 +107,12 @@ <h2>Choose your Portal folder</h2>
<div class="stepBody">
<h2>Play</h2>
<p>Turn your iPhone sideways. The game fills the screen.</p>
<div class="quality" role="group" aria-label="Graphics quality">
<button type="button" data-quality="standard">Standard</button>
<button type="button" data-quality="hd">HD</button>
<button type="button" data-quality="max">Max</button>
</div>
<p id="qualityHint"></p>
<div class="actions">
<button id="launch" type="button" disabled>Launch upstream baseline</button>
<button id="reload" class="secondary" type="button">Reload isolation</button>
Expand Down Expand Up @@ -231,15 +241,38 @@ <h3>Diagnostics</h3>
await navigator.serviceWorker.ready;
if(registration.waiting)registration.waiting.postMessage({type:'SKIP_WAITING'});
set('sw',true,navigator.serviceWorker.controller?'controlling':'registered');
if(!navigator.serviceWorker.controller&&!sessionStorage.getItem('render360PagesReloaded')){
sessionStorage.setItem('render360PagesReloaded','1');
log('Service worker installed; reloading once for isolation.');
location.reload();
return new Promise(()=>{});
// The isolation headers only apply to pages the worker controls. After
// a deploy Safari can load the page before the new worker takes over,
// leaving it without SharedArrayBuffer, so reload whenever isolation is
// missing. The time guard stops a loop on browsers that can't isolate.
if(!navigator.serviceWorker.controller||!window.crossOriginIsolated){
let last=0;try{last=Number(sessionStorage.getItem('render360PagesReloadAt'))||0}catch(_){}
if(Date.now()-last>20000){
try{sessionStorage.setItem('render360PagesReloadAt',String(Date.now()))}catch(_){}
log('Service worker not isolating this page yet; reloading.');
location.reload();
return new Promise(()=>{});
}
}
}catch(error){set('sw',false,'registration failed');log('Service worker error:',error?.stack||String(error))}
}

const QUALITY_HINTS={
standard:'540p, lighter textures. Smoothest, and safest on older iPhones.',
hd:'720p, sharper textures. Uses more memory and battery.',
max:'Full screen resolution and full textures. May run out of memory on older iPhones.'
};
function setupQuality(){
let current='standard';
try{current=localStorage.getItem('render360Quality')||'standard'}catch(_){}
if(!QUALITY_HINTS[current])current='standard';
const buttons=[...document.querySelectorAll('.quality button')];
const show=()=>{for(const b of buttons)b.setAttribute('aria-pressed',String(b.dataset.quality===current));$('qualityHint').textContent=QUALITY_HINTS[current]};
for(const b of buttons)b.addEventListener('click',()=>{current=b.dataset.quality;try{localStorage.setItem('render360Quality',current)}catch(_){}show()});
show();
}
setupQuality();

function testSharedMemory(){
try{const memory=new WebAssembly.Memory({initial:1,maximum:1,shared:true});return !!memory.buffer&&Object.prototype.toString.call(memory.buffer).includes('SharedArrayBuffer')}
catch(error){log('Wasm shared-memory test:',String(error));return false}
Expand Down
23 changes: 21 additions & 2 deletions emscripten/pre.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,24 @@ Module['arguments'].push(
// engine's own on-screen touch controls are the only way to move or look.
// They default to off everywhere except Android. Window-only: this file also
// runs inside every pthread worker.
// Graphics quality, picked on the start page or in the in-game menu.
// standard: 540p, quarter-size textures (the safe default for older phones)
// hd: 720p, half-size textures
// max: the screen's full resolution, full-size textures (uses the most memory)
const RENDER360_QUALITY = {
standard: { phoneHeight: 540, desktopHeight: 720, picmip: 2 },
hd: { phoneHeight: 720, desktopHeight: 1080, picmip: 1 },
max: { phoneHeight: 4096, desktopHeight: 4096, picmip: 0 }
}
let render360Quality = 'standard'
if(render360IsWindow) {
try {
const saved = localStorage.getItem('render360Quality')
if(saved && RENDER360_QUALITY[saved]) render360Quality = saved
} catch(_) {}
Module.render360Quality = render360Quality
}

if(render360IsWindow) {
let coarsePointer = false
try { coarsePointer = !!(window.matchMedia && window.matchMedia('(pointer: coarse)').matches) } catch(_) {}
Expand All @@ -320,7 +338,7 @@ if(render360IsWindow) {
// Quarter-size textures. On a 6 inch screen the difference is hard to
// see, and textures are the largest thing a map load adds to both the
// Wasm heap and GPU memory, which is where iOS kills the page.
Module['arguments'].push('+mat_picmip', '2')
Module['arguments'].push('+mat_picmip', String(RENDER360_QUALITY[render360Quality].picmip))
// Cheaper rendering features for a phone GPU. Bump maps and specular
// stay on: turning them off makes Portal's materials ask for a
// $bumpmap texture that is not loaded (console errors).
Expand Down Expand Up @@ -348,7 +366,8 @@ if(render360IsWindow) {
} catch(_) {}
if(longSide > 0 && shortSide > 0) {
const dpr = Math.max(1, Number(window.devicePixelRatio || 1))
const height = Math.max(360, Math.min(phone ? 540 : 720, Math.round(shortSide * dpr)))
const quality = RENDER360_QUALITY[render360Quality]
const height = Math.max(360, Math.min(phone ? quality.phoneHeight : quality.desktopHeight, Math.round(shortSide * dpr)))
const width = Math.round(height * longSide / shortSide / 2) * 2
Module.render360GameSize = { width, height }
Module['arguments'].push('-w', String(width), '-h', String(height))
Expand Down
38 changes: 37 additions & 1 deletion emscripten/shell.html
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ <h1 id="r360-error-title">Portal stopped</h1>
<div class="r360-sep"></div>
<span id="r360-fullscreen-row"><input type="button" class="r360-row" value="Fullscreen" onclick="render360OpenSheet(false); render360RequestFullscreen()"></span>
<button type="button" class="r360-row" id="r360-controller-row" onclick="render360OpenSheet(false); window.render360ToggleController && render360ToggleController()">Show controller</button>
<button type="button" class="r360-row" id="r360-quality-row" onclick="render360CycleQuality()">Graphics: Standard</button>
<button type="button" class="r360-row" onclick="render360CopyDiagnostics()">Copy diagnostics</button>
<div id="render360_diagnostic_status">Shares the engine log with the developer</div>
<div class="r360-sep"></div>
Expand Down Expand Up @@ -344,7 +345,30 @@ <h1 id="r360-error-title">Portal stopped</h1>
}

// ---------------------------------------------------------- menu
var RENDER360_QUALITY_NAMES = { standard: 'Standard', hd: 'HD', max: 'Max' };
var RENDER360_QUALITY_ORDER = ['standard', 'hd', 'max'];
function render360SavedQuality() {
var q = 'standard';
try { q = localStorage.getItem('render360Quality') || 'standard'; } catch (_) {}
return RENDER360_QUALITY_NAMES[q] ? q : 'standard';
}
function render360ShowQuality() {
var row = document.getElementById('r360-quality-row');
if (!row) return;
var saved = render360SavedQuality();
var running = (window.Module && Module.render360Quality) || saved;
row.textContent = 'Graphics: ' + RENDER360_QUALITY_NAMES[saved] +
(saved !== running ? ' (next launch)' : '');
}
// Resolution and texture size are fixed when the engine starts, so a new
// choice applies the next time Portal is opened.
function render360CycleQuality() {
var next = RENDER360_QUALITY_ORDER[(RENDER360_QUALITY_ORDER.indexOf(render360SavedQuality()) + 1) % RENDER360_QUALITY_ORDER.length];
try { localStorage.setItem('render360Quality', next); } catch (_) {}
render360ShowQuality();
}
function render360OpenSheet(open) {
if (open) render360ShowQuality();
document.getElementById('r360-sheet').classList.toggle('r360-open', !!open);
}

Expand Down Expand Up @@ -410,7 +434,8 @@ <h1 id="r360-error-title">Portal stopped</h1>
'OffscreenCanvas=' + typeof OffscreenCanvas,
'WebGL2=' + String(!!document.createElement('canvas').getContext('webgl2')),
'viewport=' + window.innerWidth + 'x' + window.innerHeight + '@' + (window.devicePixelRatio || 1) +
' game=' + JSON.stringify((typeof Module !== 'undefined' && Module.render360GameSize) || null),
' game=' + JSON.stringify((typeof Module !== 'undefined' && Module.render360GameSize) || null) +
' quality=' + ((typeof Module !== 'undefined' && Module.render360Quality) || 'unknown'),
'',
'--- latest runtime event only ---',
render360LatestOutput || '<none>'
Expand Down Expand Up @@ -555,6 +580,17 @@ <h1 id="r360-error-title">Portal stopped</h1>
' OffscreenCanvas=' + typeof OffscreenCanvas);

if (!window.crossOriginIsolated || typeof SharedArrayBuffer === 'undefined') {
// Usually the start page's service worker wasn't in control yet (Safari
// right after a deploy). Reload the whole page once so it is; the time
// guard stops a loop where isolation is impossible (Private tabs).
var render360LastIsolationReload = 0;
try { render360LastIsolationReload = Number(sessionStorage.getItem('render360PagesReloadAt')) || 0; } catch (_) {}
if (Date.now() - render360LastIsolationReload > 20000) {
try { sessionStorage.setItem('render360PagesReloadAt', String(Date.now())); } catch (_) {}
render360AppendOutput('[Render360 baseline] not isolated; reloading the start page');
try { (window.top || window).location.reload(); } catch (_) { window.location.reload(); }
throw new Error('Render360: reloading for cross-origin isolation');
}
render360ShowFailure('Portal can’t start here',
'This page is not cross-origin isolated, so threads are unavailable. Open it from the Portal start page, not a Private tab.');
}
Expand Down
5 changes: 5 additions & 0 deletions materialsystem/shaderapidx9/shaderdevicedx8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1462,10 +1462,15 @@ bool CShaderDeviceMgrDx8::ValidateMode( int nAdapter, const ShaderDeviceInfo_t &
// settings
GetCurrentModeInfo( &displayMode, 0 );

#ifndef __EMSCRIPTEN__
// make sure the window fits within the current video mode
// (In the browser the GL layer reports a fixed 640x480 display, while
// the canvas can be any size the page asks for; a page-sized window
// was rejected here and the engine quit before showing anything.)
if ( ( info.m_DisplayMode.m_nWidth > displayMode.m_nWidth ) ||
( info.m_DisplayMode.m_nHeight > displayMode.m_nHeight ) )
return false;
#endif
}
else
{
Expand Down
Loading