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

ossia / score / 30157002862

25 Jul 2026 11:53AM UTC coverage: 15.406% (+0.1%) from 15.311%
30157002862

Pull #2119

github

web-flow
Merge 51e0e1b89 into 13afd939a
Pull Request #2119: gfx: caching + offscreen infrastructure (pre-scene, from #2109)

247 of 597 new or added lines in 12 files covered. (41.37%)

9 existing lines in 4 files now uncovered.

30668 of 199059 relevant lines covered (15.41%)

981.72 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
    state.surface = QRhiGles2InitParams::newFallbackSurface();
×
117
    QRhiGles2InitParams params;
×
118
    if(window)
×
119
    {
120
      params.format = window->format();
×
121
      params.window = window;
×
122
    }
×
123
    params.fallbackSurface = state.surface;
×
124

125
    score::GLCapabilities caps;
×
126
    caps.setupFormat(params.format);
×
127
    params.format.setSamples(state.samples);
×
128
    state.version = caps.qShaderVersion;
×
129
    state.rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
×
NEW
130
    if(state.rhi)
×
131
    {
NEW
132
      state.renderSize = sz;
×
NEW
133
      populateCaps(state);
×
NEW
134
      return st;
×
135
    }
UNCOV
136
  }
×
137
#endif
138

139
#if QT_HAS_VULKAN
140
  if(graphicsApi == Vulkan)
×
141
  {
142
    QRhiVulkanInitParams params;
×
143
#if defined(_WIN32)
144
    params.deviceExtensions << VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME
145
                            << VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME
146
                            << VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME
147
                            << VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME
148
                            << VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME
149
                            << VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME
150
        ;
151
#endif
152

153
    if(window)
×
154
    {
155
      params.inst = window->vulkanInstance();
×
156
      params.window = window;
×
157
    }
×
158
    else
159
    {
160
      params.inst = score::gfx::staticVulkanInstance();
×
161
    }
162
    state.version = Gfx::Settings::shaderVersionForAPI(Vulkan);
×
163

164
    // Create shared VkDevice with video decode queues BEFORE QRhi.
165
    // Use the first physical device from QVulkanInstance — this matches
166
    // what QRhi would pick by default.
167
#if defined(VK_KHR_video_decode_queue) && QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
168
    {
169
      auto sharedDev = createSharedVulkanDevice(params.inst);
170
      if(sharedDev)
171
      {
172
        QRhiVulkanNativeHandles importedHandles;
173
        importedHandles.physDev = sharedDev.physDev;
174
        importedHandles.dev = sharedDev.dev;
175
        importedHandles.gfxQueueFamilyIdx = sharedDev.gfxQueueFamilyIdx;
176
        importedHandles.gfxQueueIdx = 0;
177
        importedHandles.gfxQueue = sharedDev.gfxQueue;
178
        importedHandles.inst = params.inst;
179

180
        state.rhi = QRhi::create(QRhi::Vulkan, &params, flags, &importedHandles);
181
        if(state.rhi)
182
        {
183
          state.customDeviceCleanup = [dev = sharedDev.dev, inst = params.inst]() {
184
            if(auto fn = reinterpret_cast<PFN_vkDestroyDevice>(
185
                   inst->getInstanceProcAddr("vkDestroyDevice")))
186
              fn(dev, nullptr);
187
          };
188
          state.renderSize = sz;
189
          populateCaps(state);
190
          return st;
191
        }
192
        sharedDev.destroy();
193
      }
194
    }
195
#endif
196

197
    // Fallback: let QRhi create its own VkDevice (no video decode queues)
198
    if(!state.rhi)
×
199
      state.rhi = QRhi::create(QRhi::Vulkan, &params, flags);
×
200

NEW
201
    if(state.rhi)
×
202
    {
NEW
203
      state.renderSize = sz;
×
NEW
204
      populateCaps(state);
×
NEW
205
      return st;
×
206
    }
UNCOV
207
  }
×
208
#endif
209

210
#ifdef Q_OS_WIN
211
  if(graphicsApi == D3D11)
212
  {
213
    QRhiD3D11InitParams params;
214
#if !defined(NDEBUG)
215
    params.enableDebugLayer = true;
216
#endif
217
    // if (framesUntilTdr > 0)
218
    // {
219
    //   params.framesUntilKillingDeviceViaTdr = framesUntilTdr;
220
    //   params.repeatDeviceKill = true;
221
    // }
222
    state.version = Gfx::Settings::shaderVersionForAPI(D3D11);
223
    state.rhi = QRhi::create(QRhi::D3D11, &params, flags);
224
    if(state.rhi)
225
    {
226
      state.renderSize = sz;
227
      populateCaps(state);
228
      return st;
229
    }
230
  }
231
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
232
  else if(graphicsApi == D3D12)
233
  {
234
    QRhiD3D12InitParams params;
235
#if !defined(NDEBUG)
236
    params.enableDebugLayer = true;
237
#endif
238
    // if (framesUntilTdr > 0)
239
    // {
240
    //   params.framesUntilKillingDeviceViaTdr = framesUntilTdr;
241
    //   params.repeatDeviceKill = true;
242
    // }
243
    state.version = Gfx::Settings::shaderVersionForAPI(D3D12);
244
    state.rhi = QRhi::create(QRhi::D3D12, &params, flags);
245
    if(state.rhi)
246
    {
247
      state.renderSize = sz;
248
      populateCaps(state);
249
      return st;
250
    }
251
  }
252
#endif
253
#endif
254

255
#ifdef Q_OS_DARWIN
256
  if(graphicsApi == Metal)
257
  {
258
    QRhiMetalInitParams params;
259
    state.version = Gfx::Settings::shaderVersionForAPI(Metal);
260
    state.rhi = QRhi::create(QRhi::Metal, &params, flags);
261
    if(state.rhi)
262
    {
263
      state.renderSize = sz;
264
      populateCaps(state);
265
      return st;
266
    }
267
  }
268
#endif
269

270
  if(!state.rhi)
×
271
  {
272
    qDebug() << "Failed to create RHI backend, creating Null backend";
×
273

274
    QRhiNullInitParams params;
275
    state.version = QShaderVersion(120);
×
276
    state.rhi = QRhi::create(QRhi::Null, &params, flags);
×
277
    state.renderSize = sz;
×
278
    state.api = GraphicsApi::Null;
×
279
    populateCaps(state);
×
280
    return st;
×
281
  }
282

283
  populateCaps(state);
×
284
  return st;
×
285
}
×
286

287
static std::shared_ptr<RenderState>
288
createRenderState(QWindow& window, GraphicsApi graphicsApi)
×
289
{
290
  return createRenderState(graphicsApi, window.size(), &window);
×
291
}
292

293
ScreenNode::ScreenNode(Configuration conf, bool embedded, bool fullScreen)
×
294
    : OutputNode{}
×
295
    , m_conf{conf}
×
296
    , m_embedded{embedded}
297
    , m_fullScreen{fullScreen}
298
    , m_ownsWindow{true}
299
{
×
300
  input.push_back(new Port{this, {}, Types::Image, {}});
×
301
}
×
302

303
ScreenNode::~ScreenNode()
×
304
{
×
305
  if(m_swapChain)
×
306
  {
307
    m_swapChain->deleteLater();
×
308

309
    if(m_window)
×
310
    {
311
      m_window->m_swapChain = nullptr;
×
312
      m_window->m_hasSwapChain = false;
×
313
    }
×
314
  }
×
315

316
  if(m_window && m_window->state)
×
317
  {
318
    delete m_window->state->renderPassDescriptor;
×
319
    m_window->state->renderPassDescriptor = nullptr;
×
320

321
    delete m_depthStencil;
×
322
    m_depthStencil = nullptr;
×
323

324
    delete m_window->state->rhi;
×
325
    m_window->state->rhi = nullptr;
×
326

327
    delete m_window->state->surface;
×
328
    m_window->state->surface = nullptr;
×
329
  }
×
330
}
×
331

332
bool ScreenNode::canRender() const
×
333
{
334
  // FIXME - Graph::onReady / Graph::onResize :
335
  // swapchain is recreated even when size does not change at the
336
  // beginning
337
  return bool(m_window) /* && m_window->m_hasSwapChain */;
×
338
}
339

340
void ScreenNode::startRendering()
×
341
{
342
  if(onFps)
×
343
    onFps(0.f);
×
344
  if(m_window)
×
345
  {
346
    m_window->onRender = [this](QRhiCommandBuffer& commands) {
×
347
      if(auto r = m_window->state->renderer.lock())
×
348
      {
349
        m_window->m_canRender = r->renderers.size() > 1;
×
350
        r->render(commands);
×
351
      }
×
352
    };
×
353
  }
×
354
}
×
355

356
void ScreenNode::render()
×
357
{
358
  // Used when we don't have vsync: request an update on the Window
359
  if(m_window)
×
360
  {
361
    onRendererChange();
×
362
    m_window->render();
×
363
  }
×
364
}
×
365

366
void ScreenNode::onRendererChange()
×
367
{
368
  if(m_window)
×
369
  {
370
    if(m_window->state)
×
371
    {
372
      if(auto r = m_window->state->renderer.lock())
×
373
      {
374
        m_window->m_canRender = r->renderers.size() > 1;
×
375
        return;
×
376
      }
377
    }
×
378
  }
×
379
  m_window->m_canRender = false;
×
380
}
×
381

382
void ScreenNode::stopRendering()
×
383
{
384
  if(m_window)
×
385
  {
386
    m_window->m_canRender = false;
×
387
    m_window->onRender = [](QRhiCommandBuffer&) {};
×
388
    if(m_window->state)
×
389
      m_window->state->renderer = {};
×
390
    ////window->state->hasSwapChain = false;
391
  }
×
392
  if(onFps)
×
393
    onFps(0.f);
×
394
}
×
395

396
void ScreenNode::setRenderer(std::shared_ptr<RenderList> r)
×
397
{
398
  m_window->state->renderer = r;
×
399
}
×
400

401
RenderList* ScreenNode::renderer() const
×
402
{
403
  if(m_window && m_window->state)
×
404
    return m_window->state->renderer.lock().get();
×
405
  else
406
    return nullptr;
×
407
}
×
408

409
void ScreenNode::setScreen(QScreen* scr)
×
410
{
411
  m_screen = scr;
×
412
  if(m_window)
×
413
  {
414
    m_window->setScreen(scr);
×
415
  }
×
416
}
×
417

418
void ScreenNode::setPosition(QPoint pos)
×
419
{
420
  m_pos = pos;
×
421
  if(m_window)
×
422
  {
423
    m_window->setPosition(pos);
×
424
  }
×
425
}
×
426

427
void ScreenNode::setTitle(QString title)
×
428
{
429
  m_title = title;
×
430
  if(m_window)
×
431
  {
432
    m_window->setTitle(title);
×
433
  }
×
434
}
×
435

436
void ScreenNode::setConfiguration(Configuration conf)
×
437
{
438
  m_conf = conf;
×
439
}
×
440

441
void ScreenNode::setSwapchainFlag(Gfx::SwapchainFlag flag)
×
442
{
443
  m_swapchainFlag = flag;
×
444
}
×
445

446
void ScreenNode::setSwapchainFormat(Gfx::SwapchainFormat format)
×
447
{
448
  m_swapchainFormat = format;
×
449
}
×
450

451
void ScreenNode::setSize(QSize sz)
×
452
{
453
  m_sz = sz;
×
454
  if(m_window)
×
455
  {
456
    m_window->setGeometry({m_window->position(), sz});
×
457
  }
×
458
}
×
459

460
void ScreenNode::setRenderSize(QSize sz)
×
461
{
462
  if(sz.width() >= 1 && sz.height() >= 1)
×
463
    m_renderSz = sz;
×
464
  else
465
    m_renderSz = std::nullopt;
×
466

467
  if(m_window && m_window->onResize)
×
468
  {
469
    m_window->onResize();
×
470
  }
×
471
}
×
472

473
void ScreenNode::setFullScreen(bool b)
×
474
{
475
  m_fullScreen = b;
×
476
  if(m_window)
×
477
  {
478
    if(b)
×
479
    {
480
      m_window->showFullScreen();
×
481
    }
×
482
    else
483
    {
484
      m_window->showNormal();
×
485
    }
486
  }
×
487
}
×
488

489
void ScreenNode::setCursor(bool b)
×
490
{
491
  if(m_window)
×
492
  {
493
    if(b && m_window->cursor() == Qt::BlankCursor)
×
494
    {
495
      m_window->unsetCursor();
×
496
    }
×
497
    else if(!b && m_window->cursor() != Qt::BlankCursor)
×
498
    {
499
      m_window->setCursor(Qt::BlankCursor);
×
500
    }
×
501
  }
×
502
}
×
503

504
void ScreenNode::createOutput(score::gfx::OutputConfiguration conf)
×
505
{
506
  if(m_ownsWindow)
×
507
  {
508
    m_window = std::make_shared<Window>(conf.graphicsApi);
×
509
    if(m_embedded)
×
510
      m_window->unsetCursor();
×
511
  }
×
512

513
  QObject::connect(m_window.get(), &Window::xChanged, [this](int x) {
×
514
    if(onWindowMove)
×
515
      onWindowMove(QPointF(x, m_window->y()));
×
516
  });
×
517
  QObject::connect(m_window.get(), &Window::yChanged, [this](int y) {
×
518
    if(onWindowMove)
×
519
      onWindowMove(QPointF(m_window->x(), y));
×
520
  });
×
521
  QObject::connect(m_window.get(), &Window::mouseMove, [this](QPointF s, QPointF w) {
×
522
    if(onMouseMove)
×
523
      onMouseMove(s, w);
×
524
  });
×
525
  QObject::connect(m_window.get(), &Window::tabletMove, [this](QTabletEvent* e) {
×
526
    if(onTabletMove)
×
527
      onTabletMove(e);
×
528
  });
×
529
  QObject::connect(m_window.get(), &Window::key, [this](int k, const QString& t) {
×
530
    if(onKey)
×
531
      onKey(k, t);
×
532
  });
×
533
  QObject::connect(m_window.get(), &Window::keyRelease, [this](int k, const QString& t) {
×
534
    if(onKeyRelease)
×
535
      onKeyRelease(k, t);
×
536
  });
×
537
  QObject::connect(m_window.get(), &Window::fps, [this](float f) {
×
538
    if(onFps)
×
539
      onFps(f);
×
540
  });
×
541
  m_window->onUpdate = this->m_vsyncCallback;
×
542
  m_window->onWindowReady = [this, graphicsApi=conf.graphicsApi, onReady = std::move(conf.onReady)] {
×
543
    m_window->state = createRenderState(*m_window, graphicsApi);
×
544
    m_window->state->window = m_window;
×
545
    m_window->state->renderSize = QSize(1280, 720);
×
546
    m_window->state->renderFormat = (m_swapchainFormat != Gfx::SwapchainFormat::SDR)
×
547
        ? QRhiTexture::RGBA32F : QRhiTexture::RGBA8;
548
    if(m_window->state->rhi)
×
549
    {
550
      // TODO depth stencil, render buffer, etc ?
551
      m_swapChain = m_window->state->rhi->newSwapChain();
×
552
      m_swapChain->setName("ScreenNode::m_swapChain");
×
553
#if QT_VERSION > QT_VERSION_CHECK(6,4,0)
554
      m_swapChain->setFormat((QRhiSwapChain::Format)m_swapchainFormat);
×
555
#endif
556
      m_window->m_swapChain = m_swapChain;
×
557
      m_depthStencil = m_window->state->rhi->newRenderBuffer(
×
558
          QRhiRenderBuffer::DepthStencil,
559
          QSize(), // no need to set the size here, due to UsedWithSwapChainOnly
×
560
          m_window->state->samples, QRhiRenderBuffer::UsedWithSwapChainOnly);
×
561
      m_depthStencil->setName("ScreenNode::m_depthStencil");
×
562
      m_swapChain->setWindow(m_window.get());
×
563
      m_swapChain->setDepthStencil(m_depthStencil);
×
564
      m_swapChain->setSampleCount(m_window->state->samples);
×
565

566
      QRhiSwapChain::Flags flags = QRhiSwapChain::MinimalBufferCount;
×
567
      if(!score::AppContext().settings<Gfx::Settings::Model>().getVSync())
×
568
        flags |= QRhiSwapChain::NoVSync;
×
569
      if(m_swapchainFlag == Gfx::SwapchainFlag::sRGB)
×
570
        flags |= QRhiSwapChain::sRGB;
×
571
      m_swapChain->setFlags(flags);
×
572

573
      m_window->state->renderPassDescriptor
×
574
          = m_swapChain->newCompatibleRenderPassDescriptor();
×
575
      m_swapChain->setRenderPassDescriptor(m_window->state->renderPassDescriptor);
×
576

577
      onReady();
×
578
    }
×
579
  };
×
580
  m_window->onResize = [this, onResize = std::move(conf.onResize)] {
×
581
    if(m_window && m_window->state)
×
582
    {
583
      auto& st = *m_window->state;
×
584
      if(!this->m_renderSz)
×
585
      {
586
        st.renderSize = st.outputSize;
×
587
      }
×
588
      else
589
      {
590
        st.renderSize = *this->m_renderSz;
×
591
      }
592
    }
×
593

594
    if(onResize)
×
595
    {
596
      onResize();
×
597
    }
×
598
  };
×
599

600
  if(!m_embedded)
×
601
  {
602
    /*
603
    if(window->isExposed())
604
    {
605
      window->exposeEvent(nullptr);
606
    }
607
    */
608

609
    if(!m_title.isEmpty())
×
610
      m_window->setTitle(m_title);
×
611

612
    if(m_screen)
×
613
    {
614
      m_window->setScreen(m_screen);
×
615
    }
×
616

617
    if(m_fullScreen)
×
618
    {
619
      m_window->showFullScreen();
×
620
    }
×
621
    else
622
    {
623
      if(m_pos)
×
624
      {
625
        m_window->setPosition(*m_pos);
×
626
      }
×
627

628
      if(!m_sz)
×
629
      {
630
        m_window->resize(1280, 720);
×
631
      }
×
632
      else
633
      {
634
        m_window->setGeometry({m_window->position(), *m_sz});
×
635
      }
636
      m_window->show();
×
637
    }
638
  }
×
639
}
×
640

641
void ScreenNode::destroyOutput()
×
642
{
643
  if(!m_window)
×
644
    return;
×
645

646
  delete m_depthStencil;
×
647
  m_depthStencil = nullptr;
×
648

649
  if(m_window)
×
650
  {
651
    if(auto s = m_window->state)
×
652
    {
653
      delete s->renderPassDescriptor;
×
654
      s->renderPassDescriptor = nullptr;
×
655
    }
×
656
  }
×
657

658
  //delete s.renderBuffer;
659
  //s.renderBuffer = nullptr;
660

661
  delete m_swapChain;
×
662
  m_swapChain = nullptr;
×
663

664
  if(m_window)
×
665
  {
666
    m_window->m_swapChain = nullptr;
×
667
  }
×
668

669
  if(m_window)
×
670
  {
671
    if(auto s = m_window->state)
×
672
    {
673
      s->destroy();
×
674
    }
×
675
  }
×
676

677
  if(m_ownsWindow)
×
678
  {
679
    m_window.reset();
×
680
  }
×
681
}
×
682

683
void ScreenNode::updateGraphicsAPI(GraphicsApi api)
×
684
{
685
  if(!m_window)
×
686
    return;
×
687

688
  if(m_window->api() != api)
×
689
  {
690
    destroyOutput();
×
691
  }
×
692
  else if(this->m_window)
×
693
  {
694
    if(auto& s = this->m_window->state)
×
695
    {
696
      // FIXME refactor with createRenderState
697
      // FIXME implement for other output nodes
698
      int samples_request
×
699
          = score::AppContext().settings<Gfx::Settings::Model>().resolveSamples(api);
×
700

701
      if(!s->rhi)
×
702
        return;
×
703
      const auto supported = s->rhi->supportedSampleCounts();
×
704
      if(supported.isEmpty())
×
705
      {
706
        samples_request = 1;
×
707
      }
×
708
      else
709
      {
710
        int chosen = supported.first();
×
711
        for(int v : supported)
×
712
        {
713
          if(v == samples_request)
×
714
          {
715
            chosen = v;
×
716
            break;
×
717
          }
718
          if(v < samples_request)
×
719
            chosen = v;
×
720
        }
721
        if(chosen != samples_request)
×
722
        {
723
          qWarning() << "updateGraphicsAPI: requested samples=" << samples_request
×
724
                     << "not in supported list" << supported << "— clamping to"
×
725
                     << chosen;
×
726
          samples_request = chosen;
×
727
        }
×
728
      }
729

730
      if(this->m_window->state->samples != samples_request)
×
731
      {
732
        destroyOutput();
×
733
      }
×
734
    }
×
735
  }
×
736
}
×
737

738
void ScreenNode::setVSyncCallback(std::function<void ()> f)
×
739
{
740
  // TODO thread safety if vulkan uses a thread ?
741
  // If we have more than one output, then instead we sync them with
742
  // a simple timer, as they may have drastically different vsync rates.
743
  m_vsyncCallback = f;
×
744
  if(m_window)
×
745
    m_window->onUpdate = m_vsyncCallback;
×
746
}
×
747

748
std::shared_ptr<score::gfx::RenderState> ScreenNode::renderState() const
×
749
{
750
  if(m_window && m_window->m_swapChain)
×
751
    return m_window->state;
×
752
  return nullptr;
×
753
}
×
754

755
score::gfx::OutputNodeRenderer* ScreenNode::createRenderer(RenderList& r) const noexcept
×
756
{
757
  score::gfx::TextureRenderTarget rt;
×
758
  rt.renderTarget = m_swapChain->currentFrameRenderTarget();
×
759
  rt.renderPass = r.state.renderPassDescriptor;
×
760
  // FIXME why doesn't it work?
761
  // return new BasicRenderer{rt, r.state, *this};
762
  return new Gfx::ScaledRenderer{rt, r.state, *this};
×
763
}
×
764

765
OutputNode::Configuration ScreenNode::configuration() const noexcept
×
766
{
767
  return m_conf;
×
768
}
769

770

771
}
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