• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 30213925900

26 Jul 2026 06:03PM UTC coverage: 15.298% (-0.01%) from 15.311%
30213925900

push

github

jcelerier
3rdparty: bump libossia for the network-context poll fix

Brings in ossia/libossia#913: SDL joystick init no longer requires haptic
support (which the Emscripten SDL2 port does not have), and
network_context::poll() restarts the io_context, without which the
WebAssembly polling path stops after its first tick.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019oPj1zRcxSQX7FNni7EHM6

30400 of 198713 relevant lines covered (15.3%)

997.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/plugins/score-plugin-gfx/Gfx/Graph/ScreenNode.cpp
1
#include <Gfx/Graph/Graph.hpp>
2
#include <Gfx/Graph/NodeRenderer.hpp>
3
#include <Gfx/Graph/OutputNode.hpp>
4
#include <Gfx/Graph/ScreenNode.hpp>
5
#include <Gfx/Graph/Window.hpp>
6
#include <Gfx/Settings/Model.hpp>
7

8
#include <score/application/GUIApplicationContext.hpp>
9
#include <score/gfx/OpenGL.hpp>
10

11
#include <QtGui/private/qrhinull_p.h>
12

13
#ifndef QT_NO_OPENGL
14
#include <Gfx/Settings/Model.hpp>
15

16
#include <Gfx/InvertYRenderer.hpp>
17
#include <QOffscreenSurface>
18
#include <QtGui/private/qrhigles2_p.h>
19
#endif
20

21
#include <score/gfx/Vulkan.hpp>
22
#if QT_HAS_VULKAN
23
#if __has_include(<QtGui/private/qrhivulkan_p.h>)
24
#include <Gfx/Graph/VulkanVideoDevice.hpp>
25
#include <QtGui/private/qrhivulkan_p.h>
26
#if __has_include(<vulkan/vulkan_win32.h>)
27
#include <vulkan/vulkan.h>
28
#ifdef Q_OS_WIN
29
#include <vulkan/vulkan_win32.h>
30
#endif
31
#endif
32
#else
33
#undef QT_HAS_VULKAN
34
#endif
35
#endif
36

37
#ifdef Q_OS_WIN
38
#include <QtGui/private/qrhid3d11_p.h>
39
#endif
40

41
#ifdef Q_OS_DARWIN
42
#include <QtGui/private/qrhimetal_p.h>
43
#endif
44

45
#include <QOffscreenSurface>
46
#include <QScreen>
47
#include <QWindow>
48

49
namespace score::gfx
50
{
51
std::shared_ptr<RenderState>
52
createRenderState(GraphicsApi graphicsApi, QSize sz, QWindow* window)
×
53
{
54
  auto st = std::make_shared<RenderState>();
×
55
  RenderState& state = *st;
×
56
  state.api = graphicsApi;
×
57

58
  const auto& settings = score::AppContext().settings<Gfx::Settings::Model>();
×
59
  state.samples = settings.resolveSamples(graphicsApi);
×
60

61
  auto populateCaps = [](RenderState& s) {
×
62
#if QT_VERSION >= QT_VERSION_CHECK(6, 12, 0)
63
    if(s.rhi)
64
    {
65
      s.caps.drawIndirect = s.rhi->isFeatureSupported(QRhi::DrawIndirect);
66
      s.caps.drawIndirectMulti = s.rhi->isFeatureSupported(QRhi::DrawIndirectMulti);
67
    }
68
#endif
69
    // Clamp the requested sample count against what the hardware actually
70
    // supports. Without this, asking for e.g. 16x MSAA on a card that only
71
    // does 8x silently mismatches between the value stored in
72
    // RenderList::m_samples (16) and what QRhi actually uses on render
73
    // targets (8 — clamped via effectiveSampleCount), which breaks
74
    // pipeline/RT sample-count matching on Vulkan.
75
    if(s.rhi && s.samples > 1)
×
76
    {
77
      const auto supported = s.rhi->supportedSampleCounts();
×
78
      if(supported.isEmpty())
×
79
      {
80
        s.samples = 1;
×
81
      }
×
82
      else
83
      {
84
        // supportedSampleCounts() is sorted ascending. Pick exact match or
85
        // the largest value <= requested; otherwise fall back to smallest.
86
        int chosen = supported.first();
×
87
        for(int v : supported)
×
88
        {
89
          if(v == s.samples)
×
90
          {
91
            chosen = v;
×
92
            break;
×
93
          }
94
          if(v < s.samples)
×
95
            chosen = v;
×
96
        }
97
        if(chosen != s.samples)
×
98
        {
99
          qWarning() << "createRenderState: requested samples=" << s.samples
×
100
                     << "not in supported list" << supported
×
101
                     << "— clamping to" << chosen;
×
102
          s.samples = chosen;
×
103
        }
×
104
      }
105
    }
×
106
  };
×
107

108
  QRhi::Flags flags{};
×
109
#ifndef NDEBUG
110
  flags |= QRhi::EnableDebugMarkers;
×
111
#endif
112

113
#ifndef QT_NO_OPENGL
114
  if(graphicsApi == OpenGL)
×
115
  {
116
    QRhiGles2InitParams params;
×
117
    if(window)
×
118
    {
119
      params.format = window->format();
×
120
      params.window = window;
×
121
    }
×
122

123
#if defined(__EMSCRIPTEN__)
124
    // Qt for wasm binds a QOpenGLContext to the *first* surface it is ever made
125
    // current with (QWasmOpenGLContext::m_contextOwningSurface) and then refuses
126
    // any other: makeCurrent() returns false and, crucially, isValid() also
127
    // returns false. QRhiGles2::ensureContext() reads that pair as a lost
128
    // context and latches contextLost = true forever.
129
    //
130
    // QRhiGles2 switches to its fallback surface exactly when its own context is
131
    // not the currently current one:
132
    //
133
    //   if (!surface) {
134
    //     if (currentSurfaceForCurrentContext(ctx)) return true;
135
    //     surface = evaluateFallbackSurface();   // the QOffscreenSurface
136
    //
137
    // With a single GL output that never happens -- the context stays current
138
    // after beginFrame, so every off-frame resource creation takes the early
139
    // return. With two GL outputs (a window device plus the Inspector's texture
140
    // preview, say) they take "current" from each other, so as soon as one of
141
    // them creates a resource outside a frame it is sent to its offscreen
142
    // fallback, makeCurrent fails, and that output is dead for good: black from
143
    // creation, never recovering, until the device is deleted and recreated.
144
    //
145
    // Give it a single surface to live on and the mismatch cannot arise. Note
146
    // this is not the browser losing a context: the real WebGL contexts stay
147
    // healthy and no webglcontextlost event is ever delivered.
148
    if(window)
149
    {
150
      params.fallbackSurface = window;
151
    }
152
    else
153
    {
154
      state.surface = QRhiGles2InitParams::newFallbackSurface();
155
      params.fallbackSurface = state.surface;
156
    }
157
#else
158
    state.surface = QRhiGles2InitParams::newFallbackSurface();
×
159
    params.fallbackSurface = state.surface;
×
160
#endif
161

162
    score::GLCapabilities caps;
×
163
    caps.setupFormat(params.format);
×
164
    params.format.setSamples(state.samples);
×
165
    state.version = caps.qShaderVersion;
×
166
    state.rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
×
167
    if(!state.rhi)
×
168
    {
169
      // Browsers cap the number of simultaneous WebGL contexts (Chrome: 16)
170
      // and score creates one QRhi -- hence one context -- per output window,
171
      // on top of the one every OpenGL QWindow already needs. Past the cap
172
      // context creation fails, and until now that failure was silent: no
173
      // swapchain was created, Window::render() returned immediately and the
174
      // output window stayed black for the rest of its life.
175
      qCritical() << "createRenderState: QRhi::create(OpenGLES2) FAILED. "
×
176
                     "This output will never render.";
177
    }
×
178
    state.renderSize = sz;
×
179
    populateCaps(state);
×
180
    return st;
×
181
  }
×
182
#endif
183

184
#if QT_HAS_VULKAN
185
  if(graphicsApi == Vulkan)
×
186
  {
187
    QRhiVulkanInitParams params;
×
188
#if defined(_WIN32)
189
    params.deviceExtensions << VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME
190
                            << VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME
191
                            << VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME
192
                            << VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME
193
                            << VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME
194
                            << VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME
195
        ;
196
#endif
197

198
    if(window)
×
199
    {
200
      params.inst = window->vulkanInstance();
×
201
      params.window = window;
×
202
    }
×
203
    else
204
    {
205
      params.inst = score::gfx::staticVulkanInstance();
×
206
    }
207
    state.version = Gfx::Settings::shaderVersionForAPI(Vulkan);
×
208

209
    // Create shared VkDevice with video decode queues BEFORE QRhi.
210
    // Use the first physical device from QVulkanInstance — this matches
211
    // what QRhi would pick by default.
212
#if defined(VK_KHR_video_decode_queue) && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
213
    {
214
      auto sharedDev = createSharedVulkanDevice(params.inst);
215
      if(sharedDev)
216
      {
217
        QRhiVulkanNativeHandles importedHandles;
218
        importedHandles.physDev = sharedDev.physDev;
219
        importedHandles.dev = sharedDev.dev;
220
        importedHandles.gfxQueueFamilyIdx = sharedDev.gfxQueueFamilyIdx;
221
        importedHandles.gfxQueueIdx = 0;
222
        importedHandles.gfxQueue = sharedDev.gfxQueue;
223
        importedHandles.inst = params.inst;
224

225
        state.rhi = QRhi::create(QRhi::Vulkan, &params, flags, &importedHandles);
226
        if(state.rhi)
227
        {
228
          state.customDeviceCleanup = [dev = sharedDev.dev, inst = params.inst]() {
229
            if(auto fn = reinterpret_cast<PFN_vkDestroyDevice>(
230
                   inst->getInstanceProcAddr("vkDestroyDevice")))
231
              fn(dev, nullptr);
232
          };
233
          state.renderSize = sz;
234
          populateCaps(state);
235
          return st;
236
        }
237
        sharedDev.destroy();
238
      }
239
    }
240
#endif
241

242
    // Fallback: let QRhi create its own VkDevice (no video decode queues)
243
    if(!state.rhi)
×
244
      state.rhi = QRhi::create(QRhi::Vulkan, &params, flags);
×
245

246
    state.renderSize = sz;
×
247
    populateCaps(state);
×
248
    return st;
×
249
  }
×
250
#endif
251

252
#ifdef Q_OS_WIN
253
  if(graphicsApi == D3D11)
254
  {
255
    QRhiD3D11InitParams params;
256
#if !defined(NDEBUG)
257
    params.enableDebugLayer = true;
258
#endif
259
    // if (framesUntilTdr > 0)
260
    // {
261
    //   params.framesUntilKillingDeviceViaTdr = framesUntilTdr;
262
    //   params.repeatDeviceKill = true;
263
    // }
264
    state.version = Gfx::Settings::shaderVersionForAPI(D3D11);
265
    state.rhi = QRhi::create(QRhi::D3D11, &params, flags);
266
    state.renderSize = sz;
267
    populateCaps(state);
268
    return st;
269
  }
270
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
271
  else if(graphicsApi == D3D12)
272
  {
273
    QRhiD3D12InitParams params;
274
#if !defined(NDEBUG)
275
    params.enableDebugLayer = true;
276
#endif
277
    // if (framesUntilTdr > 0)
278
    // {
279
    //   params.framesUntilKillingDeviceViaTdr = framesUntilTdr;
280
    //   params.repeatDeviceKill = true;
281
    // }
282
    state.version = Gfx::Settings::shaderVersionForAPI(D3D12);
283
    state.rhi = QRhi::create(QRhi::D3D12, &params, flags);
284
    state.renderSize = sz;
285
    populateCaps(state);
286
    return st;
287
  }
288
#endif
289
#endif
290

291
#ifdef Q_OS_DARWIN
292
  if(graphicsApi == Metal)
293
  {
294
    QRhiMetalInitParams params;
295
    state.version = Gfx::Settings::shaderVersionForAPI(Metal);
296
    state.rhi = QRhi::create(QRhi::Metal, &params, flags);
297
    state.renderSize = sz;
298
    populateCaps(state);
299
    return st;
300
  }
301
#endif
302

303
  if(!state.rhi)
×
304
  {
305
    qDebug() << "Failed to create RHI backend, creating Null backend";
×
306

307
    QRhiNullInitParams params;
308
    state.version = QShaderVersion(120);
×
309
    state.rhi = QRhi::create(QRhi::Null, &params, flags);
×
310
    state.renderSize = sz;
×
311
    state.api = GraphicsApi::Null;
×
312
    populateCaps(state);
×
313
    return st;
×
314
  }
315

316
  populateCaps(state);
×
317
  return st;
×
318
}
×
319

320
static std::shared_ptr<RenderState>
321
createRenderState(QWindow& window, GraphicsApi graphicsApi)
×
322
{
323
  return createRenderState(graphicsApi, window.size(), &window);
×
324
}
325

326
ScreenNode::ScreenNode(Configuration conf, bool embedded, bool fullScreen)
×
327
    : OutputNode{}
×
328
    , m_conf{conf}
×
329
    , m_embedded{embedded}
330
    , m_fullScreen{fullScreen}
331
    , m_ownsWindow{true}
332
{
×
333
  input.push_back(new Port{this, {}, Types::Image, {}});
×
334
}
×
335

336
ScreenNode::~ScreenNode()
×
337
{
×
338
  if(m_swapChain)
×
339
  {
340
    m_swapChain->deleteLater();
×
341

342
    if(m_window)
×
343
    {
344
      m_window->m_swapChain = nullptr;
×
345
      m_window->m_hasSwapChain = false;
×
346
    }
×
347
  }
×
348

349
  if(m_window && m_window->state)
×
350
  {
351
    delete m_window->state->renderPassDescriptor;
×
352
    m_window->state->renderPassDescriptor = nullptr;
×
353

354
    delete m_depthStencil;
×
355
    m_depthStencil = nullptr;
×
356

357
    delete m_window->state->rhi;
×
358
    m_window->state->rhi = nullptr;
×
359

360
    delete m_window->state->surface;
×
361
    m_window->state->surface = nullptr;
×
362
  }
×
363
}
×
364

365
bool ScreenNode::canRender() const
×
366
{
367
  // The swapchain must have been through a successful createOrResize() before
368
  // the render list is built against it: otherwise every render target is
369
  // sized from an empty swapchain and the window stays black until something
370
  // else happens to rebuild the graph.
371
  return bool(m_window) && m_window->m_hasSwapChain;
×
372
}
373

374
void ScreenNode::startRendering()
×
375
{
376
  if(onFps)
×
377
    onFps(0.f);
×
378
  if(m_window)
×
379
  {
380
    m_window->onRender = [this](QRhiCommandBuffer& commands) {
×
381
      if(auto r = m_window->state->renderer.lock())
×
382
      {
383
        m_window->m_canRender = r->renderers.size() > 1;
×
384
        r->render(commands);
×
385
      }
×
386
    };
×
387
  }
×
388
}
×
389

390
void ScreenNode::render()
×
391
{
392
  // Used when we don't have vsync: request an update on the Window
393
  if(m_window)
×
394
  {
395
    // Window::render() bails on a lost context by itself, so this is only about
396
    // not doing the per-tick work either: this is the second, independent
397
    // driver of the render loop (GfxContext::on_manual_timer /
398
    // on_no_vsync_timer -> ScreenNode::render), next to the window's own update
399
    // requests.
400
    if(m_window->deviceLost())
×
401
      return;
×
402

403
    onRendererChange();
×
404
    m_window->render();
×
405
  }
×
406
}
×
407

408
void ScreenNode::onRendererChange()
×
409
{
410
  if(m_window)
×
411
  {
412
    if(m_window->state)
×
413
    {
414
      if(auto r = m_window->state->renderer.lock())
×
415
      {
416
        const bool canRender = r->renderers.size() > 1;
×
417
        // Called once per frame: only report transitions.
418
        m_window->m_canRender = canRender;
×
419
        return;
×
420
      }
421
    }
×
422
  }
×
423
  m_window->m_canRender = false;
×
424
}
×
425

426
void ScreenNode::stopRendering()
×
427
{
428
  if(m_window)
×
429
  {
430
    m_window->m_canRender = false;
×
431
    m_window->onRender = [](QRhiCommandBuffer&) {};
×
432
    if(m_window->state)
×
433
      m_window->state->renderer = {};
×
434
    ////window->state->hasSwapChain = false;
435
  }
×
436
  if(onFps)
×
437
    onFps(0.f);
×
438
}
×
439

440
void ScreenNode::setRenderer(std::shared_ptr<RenderList> r)
×
441
{
442
  m_window->state->renderer = r;
×
443
}
×
444

445
RenderList* ScreenNode::renderer() const
×
446
{
447
  if(m_window && m_window->state)
×
448
    return m_window->state->renderer.lock().get();
×
449
  else
450
    return nullptr;
×
451
}
×
452

453
void ScreenNode::setScreen(QScreen* scr)
×
454
{
455
  m_screen = scr;
×
456
  if(m_window)
×
457
  {
458
    m_window->setScreen(scr);
×
459
  }
×
460
}
×
461

462
void ScreenNode::setPosition(QPoint pos)
×
463
{
464
  m_pos = pos;
×
465
  if(m_window)
×
466
  {
467
    m_window->setPosition(pos);
×
468
  }
×
469
}
×
470

471
void ScreenNode::setTitle(QString title)
×
472
{
473
  m_title = title;
×
474
  if(m_window)
×
475
  {
476
    m_window->setTitle(title);
×
477
  }
×
478
}
×
479

480
void ScreenNode::setConfiguration(Configuration conf)
×
481
{
482
  m_conf = conf;
×
483
}
×
484

485
void ScreenNode::setSwapchainFlag(Gfx::SwapchainFlag flag)
×
486
{
487
  m_swapchainFlag = flag;
×
488
}
×
489

490
void ScreenNode::setSwapchainFormat(Gfx::SwapchainFormat format)
×
491
{
492
  m_swapchainFormat = format;
×
493
}
×
494

495
void ScreenNode::setSize(QSize sz)
×
496
{
497
  m_sz = sz;
×
498
  if(m_window)
×
499
  {
500
    m_window->setGeometry({m_window->position(), sz});
×
501
  }
×
502
}
×
503

504
void ScreenNode::setRenderSize(QSize sz)
×
505
{
506
  if(sz.width() >= 1 && sz.height() >= 1)
×
507
    m_renderSz = sz;
×
508
  else
509
    m_renderSz = std::nullopt;
×
510

511
  if(m_window && m_window->onResize)
×
512
  {
513
    m_window->onResize();
×
514
  }
×
515
}
×
516

517
void ScreenNode::setFullScreen(bool b)
×
518
{
519
  m_fullScreen = b;
×
520
  if(m_window)
×
521
  {
522
    if(b)
×
523
    {
524
      m_window->showFullScreen();
×
525
    }
×
526
    else
527
    {
528
      m_window->showNormal();
×
529
    }
530
  }
×
531
}
×
532

533
void ScreenNode::setCursor(bool b)
×
534
{
535
  if(m_window)
×
536
  {
537
    if(b && m_window->cursor() == Qt::BlankCursor)
×
538
    {
539
      m_window->unsetCursor();
×
540
    }
×
541
    else if(!b && m_window->cursor() != Qt::BlankCursor)
×
542
    {
543
      m_window->setCursor(Qt::BlankCursor);
×
544
    }
×
545
  }
×
546
}
×
547

548
void ScreenNode::createOutput(score::gfx::OutputConfiguration conf)
×
549
{
550
  if(m_ownsWindow)
×
551
  {
552
    m_window = std::make_shared<Window>(conf.graphicsApi);
×
553
    if(m_embedded)
×
554
      m_window->unsetCursor();
×
555
  }
×
556

557
  QObject::connect(m_window.get(), &Window::xChanged, [this](int x) {
×
558
    if(onWindowMove)
×
559
      onWindowMove(QPointF(x, m_window->y()));
×
560
  });
×
561
  QObject::connect(m_window.get(), &Window::yChanged, [this](int y) {
×
562
    if(onWindowMove)
×
563
      onWindowMove(QPointF(m_window->x(), y));
×
564
  });
×
565
  QObject::connect(m_window.get(), &Window::mouseMove, [this](QPointF s, QPointF w) {
×
566
    if(onMouseMove)
×
567
      onMouseMove(s, w);
×
568
  });
×
569
  QObject::connect(m_window.get(), &Window::tabletMove, [this](QTabletEvent* e) {
×
570
    if(onTabletMove)
×
571
      onTabletMove(e);
×
572
  });
×
573
  QObject::connect(m_window.get(), &Window::key, [this](int k, const QString& t) {
×
574
    if(onKey)
×
575
      onKey(k, t);
×
576
  });
×
577
  QObject::connect(m_window.get(), &Window::keyRelease, [this](int k, const QString& t) {
×
578
    if(onKeyRelease)
×
579
      onKeyRelease(k, t);
×
580
  });
×
581
  QObject::connect(m_window.get(), &Window::fps, [this](float f) {
×
582
    if(onFps)
×
583
      onFps(f);
×
584
  });
×
585
  m_window->onUpdate = this->m_vsyncCallback;
×
586
  m_window->onWindowReady = [this, graphicsApi=conf.graphicsApi, onReady = std::move(conf.onReady)] {
×
587
    m_window->state = createRenderState(*m_window, graphicsApi);
×
588
    m_window->state->window = m_window;
×
589
    m_window->state->renderSize = QSize(1280, 720);
×
590
    m_window->state->renderFormat = (m_swapchainFormat != Gfx::SwapchainFormat::SDR)
×
591
        ? QRhiTexture::RGBA32F : QRhiTexture::RGBA8;
592
    if(m_window->state->rhi)
×
593
    {
594
      // TODO depth stencil, render buffer, etc ?
595
      m_swapChain = m_window->state->rhi->newSwapChain();
×
596
      m_swapChain->setName("ScreenNode::m_swapChain");
×
597
#if QT_VERSION > QT_VERSION_CHECK(6,4,0)
598
      m_swapChain->setFormat((QRhiSwapChain::Format)m_swapchainFormat);
×
599
#endif
600
      m_window->m_swapChain = m_swapChain;
×
601
      m_depthStencil = m_window->state->rhi->newRenderBuffer(
×
602
          QRhiRenderBuffer::DepthStencil,
603
          QSize(), // no need to set the size here, due to UsedWithSwapChainOnly
×
604
          m_window->state->samples, QRhiRenderBuffer::UsedWithSwapChainOnly);
×
605
      m_depthStencil->setName("ScreenNode::m_depthStencil");
×
606
      m_swapChain->setWindow(m_window.get());
×
607
      m_swapChain->setDepthStencil(m_depthStencil);
×
608
      m_swapChain->setSampleCount(m_window->state->samples);
×
609

610
      QRhiSwapChain::Flags flags = QRhiSwapChain::MinimalBufferCount;
×
611
      if(!score::AppContext().settings<Gfx::Settings::Model>().getVSync())
×
612
        flags |= QRhiSwapChain::NoVSync;
×
613
      if(m_swapchainFlag == Gfx::SwapchainFlag::sRGB)
×
614
        flags |= QRhiSwapChain::sRGB;
×
615
      m_swapChain->setFlags(flags);
×
616

617
      m_window->state->renderPassDescriptor
×
618
          = m_swapChain->newCompatibleRenderPassDescriptor();
×
619
      m_swapChain->setRenderPassDescriptor(m_window->state->renderPassDescriptor);
×
620

621
      onReady();
×
622
    }
×
623
  };
×
624
  m_window->onResize = [this, onResize = std::move(conf.onResize)] {
×
625
    if(m_window && m_window->state)
×
626
    {
627
      auto& st = *m_window->state;
×
628
      if(!this->m_renderSz)
×
629
      {
630
        st.renderSize = st.outputSize;
×
631
      }
×
632
      else
633
      {
634
        st.renderSize = *this->m_renderSz;
×
635
      }
636
    }
×
637

638
    if(onResize)
×
639
    {
640
      onResize();
×
641
    }
×
642
  };
×
643

644
  if(!m_embedded)
×
645
  {
646
    /*
647
    if(window->isExposed())
648
    {
649
      window->exposeEvent(nullptr);
650
    }
651
    */
652

653
    if(!m_title.isEmpty())
×
654
      m_window->setTitle(m_title);
×
655

656
    if(m_screen)
×
657
    {
658
      m_window->setScreen(m_screen);
×
659
    }
×
660

661
    if(m_fullScreen)
×
662
    {
663
      m_window->showFullScreen();
×
664
    }
×
665
    else
666
    {
667
      if(m_pos)
×
668
      {
669
        m_window->setPosition(*m_pos);
×
670
      }
×
671

672
      if(!m_sz)
×
673
      {
674
        m_window->resize(1280, 720);
×
675
      }
×
676
      else
677
      {
678
        m_window->setGeometry({m_window->position(), *m_sz});
×
679
      }
680
#if defined(__EMSCRIPTEN__)
681
  // Qt for wasm reports ShowIsFullScreen unconditionally, so QWindow::show()
682
  // resolves to showFullScreen(): the requested size is dropped and the
683
  // full-screen state suppresses the frame. showNormal() sets the state itself.
684
  m_window->showNormal();
685
#else
686
      m_window->show();
×
687
#endif
688
    }
689
  }
×
690
}
×
691

692
void ScreenNode::destroyOutput()
×
693
{
694
  if(!m_window)
×
695
    return;
×
696

697
  delete m_depthStencil;
×
698
  m_depthStencil = nullptr;
×
699

700
  if(m_window)
×
701
  {
702
    if(auto s = m_window->state)
×
703
    {
704
      delete s->renderPassDescriptor;
×
705
      s->renderPassDescriptor = nullptr;
×
706
    }
×
707
  }
×
708

709
  //delete s.renderBuffer;
710
  //s.renderBuffer = nullptr;
711

712
  delete m_swapChain;
×
713
  m_swapChain = nullptr;
×
714

715
  if(m_window)
×
716
  {
717
    m_window->m_swapChain = nullptr;
×
718
  }
×
719

720
  if(m_window)
×
721
  {
722
    if(auto s = m_window->state)
×
723
    {
724
      s->destroy();
×
725
    }
×
726
  }
×
727

728
  if(m_ownsWindow)
×
729
  {
730
    m_window.reset();
×
731
  }
×
732
}
×
733

734
void ScreenNode::updateGraphicsAPI(GraphicsApi api)
×
735
{
736
  if(!m_window)
×
737
    return;
×
738

739
  if(m_window->api() != api)
×
740
  {
741
    destroyOutput();
×
742
  }
×
743
  else if(this->m_window)
×
744
  {
745
    if(auto& s = this->m_window->state)
×
746
    {
747
      // FIXME refactor with createRenderState
748
      // FIXME implement for other output nodes
749
      int samples_request
×
750
          = score::AppContext().settings<Gfx::Settings::Model>().resolveSamples(api);
×
751

752
      if(!s->rhi)
×
753
        return;
×
754
      const auto supported = s->rhi->supportedSampleCounts();
×
755
      if(supported.isEmpty())
×
756
      {
757
        samples_request = 1;
×
758
      }
×
759
      else
760
      {
761
        int chosen = supported.first();
×
762
        for(int v : supported)
×
763
        {
764
          if(v == samples_request)
×
765
          {
766
            chosen = v;
×
767
            break;
×
768
          }
769
          if(v < samples_request)
×
770
            chosen = v;
×
771
        }
772
        if(chosen != samples_request)
×
773
        {
774
          qWarning() << "updateGraphicsAPI: requested samples=" << samples_request
×
775
                     << "not in supported list" << supported << "— clamping to"
×
776
                     << chosen;
×
777
          samples_request = chosen;
×
778
        }
×
779
      }
780

781
      if(this->m_window->state->samples != samples_request)
×
782
      {
783
        destroyOutput();
×
784
      }
×
785
    }
×
786
  }
×
787
}
×
788

789
void ScreenNode::setVSyncCallback(std::function<void ()> f)
×
790
{
791
  // TODO thread safety if vulkan uses a thread ?
792
  // If we have more than one output, then instead we sync them with
793
  // a simple timer, as they may have drastically different vsync rates.
794
  m_vsyncCallback = f;
×
795
  if(m_window)
×
796
    m_window->onUpdate = m_vsyncCallback;
×
797
}
×
798

799
std::shared_ptr<score::gfx::RenderState> ScreenNode::renderState() const
×
800
{
801
  if(m_window && m_window->m_swapChain)
×
802
    return m_window->state;
×
803
  return nullptr;
×
804
}
×
805

806
score::gfx::OutputNodeRenderer* ScreenNode::createRenderer(RenderList& r) const noexcept
×
807
{
808
  score::gfx::TextureRenderTarget rt;
×
809
  rt.renderTarget = m_swapChain->currentFrameRenderTarget();
×
810
  rt.renderPass = r.state.renderPassDescriptor;
×
811

812

813
  // FIXME why doesn't it work?
814
  // return new BasicRenderer{rt, r.state, *this};
815
  return new Gfx::ScaledRenderer{rt, r.state, *this, m_swapChain};
×
816
}
×
817

818
OutputNode::Configuration ScreenNode::configuration() const noexcept
×
819
{
820
  return m_conf;
×
821
}
822

823

824
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc