summaryrefslogtreecommitdiff
path: root/src/video_core
diff options
context:
space:
mode:
authorGravatar ReinUsesLisp2020-09-19 17:15:02 -0300
committerGravatar ReinUsesLisp2020-09-20 16:29:41 -0300
commit7003090187e02c8625f4eb7a024ac97f9b0159aa (patch)
treeb38f399967df79eb7c0dc711508cd54b7c9bf62e /src/video_core
parentMerge pull request #4643 from FearlessTobi/decrease-pad-update-interval (diff)
downloadyuzu-7003090187e02c8625f4eb7a024ac97f9b0159aa.tar.gz
yuzu-7003090187e02c8625f4eb7a024ac97f9b0159aa.tar.xz
yuzu-7003090187e02c8625f4eb7a024ac97f9b0159aa.zip
renderer_opengl: Remove emulated mailbox presentation
Emulated mailbox presentation was causing performance issues on Nvidia's OpenGL driver. Remove it.
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/renderer_base.h5
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp288
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h16
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h1
5 files changed, 22 insertions, 293 deletions
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 649074acd..5c650808b 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -46,11 +46,6 @@ public:
46 /// Finalize rendering the guest frame and draw into the presentation texture 46 /// Finalize rendering the guest frame and draw into the presentation texture
47 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; 47 virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0;
48 48
49 /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer
50 /// specific implementation)
51 /// Returns true if a frame was drawn
52 virtual bool TryPresent(int timeout_ms) = 0;
53
54 // Getter/setter functions: 49 // Getter/setter functions:
55 // ------------------------ 50 // ------------------------
56 51
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index a4c5b8f74..2ccca1993 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -32,20 +32,6 @@ namespace OpenGL {
32 32
33namespace { 33namespace {
34 34
35constexpr std::size_t SWAP_CHAIN_SIZE = 3;
36
37struct Frame {
38 u32 width{}; /// Width of the frame (to detect resize)
39 u32 height{}; /// Height of the frame
40 bool color_reloaded{}; /// Texture attachment was recreated (ie: resized)
41 OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO
42 OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread
43 OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread
44 GLsync render_fence{}; /// Fence created on the render thread
45 GLsync present_fence{}; /// Fence created on the presentation thread
46 bool is_srgb{}; /// Framebuffer is sRGB or RGB
47};
48
49constexpr GLint PositionLocation = 0; 35constexpr GLint PositionLocation = 0;
50constexpr GLint TexCoordLocation = 1; 36constexpr GLint TexCoordLocation = 1;
51constexpr GLint ModelViewMatrixLocation = 0; 37constexpr GLint ModelViewMatrixLocation = 0;
@@ -58,24 +44,6 @@ struct ScreenRectVertex {
58 std::array<GLfloat, 2> tex_coord; 44 std::array<GLfloat, 2> tex_coord;
59}; 45};
60 46
61/// Returns true if any debug tool is attached
62bool HasDebugTool() {
63 const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
64 if (nsight) {
65 return true;
66 }
67
68 GLint num_extensions;
69 glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
70 for (GLuint index = 0; index < static_cast<GLuint>(num_extensions); ++index) {
71 const auto name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, index));
72 if (!std::strcmp(name, "GL_EXT_debug_tool")) {
73 return true;
74 }
75 }
76 return false;
77}
78
79/** 47/**
80 * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left 48 * Defines a 1:1 pixel ortographic projection matrix with (0,0) on the top-left
81 * corner and (width, height) on the lower-bottom. 49 * corner and (width, height) on the lower-bottom.
@@ -159,135 +127,15 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit
159 127
160} // Anonymous namespace 128} // Anonymous namespace
161 129
162/**
163 * For smooth Vsync rendering, we want to always present the latest frame that the core generates,
164 * but also make sure that rendering happens at the pace that the frontend dictates. This is a
165 * helper class that the renderer uses to sync frames between the render thread and the presentation
166 * thread
167 */
168class FrameMailbox {
169public:
170 std::mutex swap_chain_lock;
171 std::condition_variable present_cv;
172 std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{};
173 std::queue<Frame*> free_queue;
174 std::deque<Frame*> present_queue;
175 Frame* previous_frame{};
176
177 FrameMailbox() {
178 for (auto& frame : swap_chain) {
179 free_queue.push(&frame);
180 }
181 }
182
183 ~FrameMailbox() {
184 // lock the mutex and clear out the present and free_queues and notify any people who are
185 // blocked to prevent deadlock on shutdown
186 std::scoped_lock lock{swap_chain_lock};
187 std::queue<Frame*>().swap(free_queue);
188 present_queue.clear();
189 present_cv.notify_all();
190 }
191
192 void ReloadPresentFrame(Frame* frame, u32 height, u32 width) {
193 frame->present.Release();
194 frame->present.Create();
195 GLint previous_draw_fbo{};
196 glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo);
197 glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle);
198 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
199 frame->color.handle);
200 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
201 LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!");
202 }
203 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo);
204 frame->color_reloaded = false;
205 }
206
207 void ReloadRenderFrame(Frame* frame, u32 width, u32 height) {
208 // Recreate the color texture attachment
209 frame->color.Release();
210 frame->color.Create();
211 const GLenum internal_format = frame->is_srgb ? GL_SRGB8 : GL_RGB8;
212 glNamedRenderbufferStorage(frame->color.handle, internal_format, width, height);
213
214 // Recreate the FBO for the render target
215 frame->render.Release();
216 frame->render.Create();
217 glBindFramebuffer(GL_FRAMEBUFFER, frame->render.handle);
218 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
219 frame->color.handle);
220 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
221 LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!");
222 }
223
224 frame->width = width;
225 frame->height = height;
226 frame->color_reloaded = true;
227 }
228
229 Frame* GetRenderFrame() {
230 std::unique_lock lock{swap_chain_lock};
231
232 // If theres no free frames, we will reuse the oldest render frame
233 if (free_queue.empty()) {
234 auto frame = present_queue.back();
235 present_queue.pop_back();
236 return frame;
237 }
238
239 Frame* frame = free_queue.front();
240 free_queue.pop();
241 return frame;
242 }
243
244 void ReleaseRenderFrame(Frame* frame) {
245 std::unique_lock lock{swap_chain_lock};
246 present_queue.push_front(frame);
247 present_cv.notify_one();
248 }
249
250 Frame* TryGetPresentFrame(int timeout_ms) {
251 std::unique_lock lock{swap_chain_lock};
252 // wait for new entries in the present_queue
253 present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
254 [&] { return !present_queue.empty(); });
255 if (present_queue.empty()) {
256 // timed out waiting for a frame to draw so return the previous frame
257 return previous_frame;
258 }
259
260 // free the previous frame and add it back to the free queue
261 if (previous_frame) {
262 free_queue.push(previous_frame);
263 }
264
265 // the newest entries are pushed to the front of the queue
266 Frame* frame = present_queue.front();
267 present_queue.pop_front();
268 // remove all old entries from the present queue and move them back to the free_queue
269 for (auto f : present_queue) {
270 free_queue.push(f);
271 }
272 present_queue.clear();
273 previous_frame = frame;
274 return frame;
275 }
276};
277
278RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_, 130RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
279 Core::Frontend::EmuWindow& emu_window_, 131 Core::Frontend::EmuWindow& emu_window_,
280 Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_, 132 Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
281 std::unique_ptr<Core::Frontend::GraphicsContext> context) 133 std::unique_ptr<Core::Frontend::GraphicsContext> context)
282 : RendererBase{emu_window_, std::move(context)}, telemetry_session{telemetry_session_}, 134 : RendererBase{emu_window_, std::move(context)}, telemetry_session{telemetry_session_},
283 emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device}, 135 emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device} {}
284 has_debug_tool{HasDebugTool()} {}
285 136
286RendererOpenGL::~RendererOpenGL() = default; 137RendererOpenGL::~RendererOpenGL() = default;
287 138
288MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
289MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
290
291void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { 139void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
292 if (!framebuffer) { 140 if (!framebuffer) {
293 return; 141 return;
@@ -296,79 +144,34 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
296 PrepareRendertarget(framebuffer); 144 PrepareRendertarget(framebuffer);
297 RenderScreenshot(); 145 RenderScreenshot();
298 146
299 Frame* frame; 147 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
300 { 148 DrawScreen(emu_window.GetFramebufferLayout());
301 MICROPROFILE_SCOPE(OpenGL_WaitPresent);
302
303 frame = frame_mailbox->GetRenderFrame();
304
305 // Clean up sync objects before drawing
306
307 // INTEL driver workaround. We can't delete the previous render sync object until we are
308 // sure that the presentation is done
309 if (frame->present_fence) {
310 glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
311 }
312
313 // delete the draw fence if the frame wasn't presented
314 if (frame->render_fence) {
315 glDeleteSync(frame->render_fence);
316 frame->render_fence = 0;
317 }
318
319 // wait for the presentation to be done
320 if (frame->present_fence) {
321 glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED);
322 glDeleteSync(frame->present_fence);
323 frame->present_fence = 0;
324 }
325 }
326 149
327 { 150 ++m_current_frame;
328 MICROPROFILE_SCOPE(OpenGL_RenderFrame);
329 const auto& layout = render_window.GetFramebufferLayout();
330 151
331 // Recreate the frame if the size of the window has changed 152 rasterizer->TickFrame();
332 if (layout.width != frame->width || layout.height != frame->height ||
333 screen_info.display_srgb != frame->is_srgb) {
334 LOG_DEBUG(Render_OpenGL, "Reloading render frame");
335 frame->is_srgb = screen_info.display_srgb;
336 frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
337 }
338 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frame->render.handle);
339 DrawScreen(layout);
340 // Create a fence for the frontend to wait on and swap this frame to OffTex
341 frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
342 glFlush();
343 frame_mailbox->ReleaseRenderFrame(frame);
344 m_current_frame++;
345 rasterizer->TickFrame();
346 }
347 153
348 render_window.PollEvents(); 154 render_window.PollEvents();
349 if (has_debug_tool) { 155 context->SwapBuffers();
350 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
351 Present(0);
352 context->SwapBuffers();
353 }
354} 156}
355 157
356void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) { 158void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) {
357 if (framebuffer) { 159 if (!framebuffer) {
358 // If framebuffer is provided, reload it from memory to a texture 160 return;
359 if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || 161 }
360 screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || 162 // If framebuffer is provided, reload it from memory to a texture
361 screen_info.texture.pixel_format != framebuffer->pixel_format || 163 if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) ||
362 gl_framebuffer_data.empty()) { 164 screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) ||
363 // Reallocate texture if the framebuffer size has changed. 165 screen_info.texture.pixel_format != framebuffer->pixel_format ||
364 // This is expected to not happen very often and hence should not be a 166 gl_framebuffer_data.empty()) {
365 // performance problem. 167 // Reallocate texture if the framebuffer size has changed.
366 ConfigureFramebufferTexture(screen_info.texture, *framebuffer); 168 // This is expected to not happen very often and hence should not be a
367 } 169 // performance problem.
368 170 ConfigureFramebufferTexture(screen_info.texture, *framebuffer);
369 // Load the framebuffer from memory, draw it to the screen, and swap buffers
370 LoadFBToScreenInfo(*framebuffer);
371 } 171 }
172
173 // Load the framebuffer from memory, draw it to the screen, and swap buffers
174 LoadFBToScreenInfo(*framebuffer);
372} 175}
373 176
374void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { 177void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) {
@@ -418,8 +221,6 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
418} 221}
419 222
420void RendererOpenGL::InitOpenGLObjects() { 223void RendererOpenGL::InitOpenGLObjects() {
421 frame_mailbox = std::make_unique<FrameMailbox>();
422
423 glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), 224 glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
424 Settings::values.bg_blue.GetValue(), 0.0f); 225 Settings::values.bg_blue.GetValue(), 0.0f);
425 226
@@ -647,51 +448,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
647 program_manager.RestoreGuestPipeline(); 448 program_manager.RestoreGuestPipeline();
648} 449}
649 450
650bool RendererOpenGL::TryPresent(int timeout_ms) {
651 if (has_debug_tool) {
652 LOG_DEBUG(Render_OpenGL,
653 "Skipping presentation because we are presenting on the main context");
654 return false;
655 }
656 return Present(timeout_ms);
657}
658
659bool RendererOpenGL::Present(int timeout_ms) {
660 const auto& layout = render_window.GetFramebufferLayout();
661 auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms);
662 if (!frame) {
663 LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present");
664 return false;
665 }
666
667 // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a
668 // readback since we won't be doing any blending
669 glClear(GL_COLOR_BUFFER_BIT);
670
671 // Recreate the presentation FBO if the color attachment was changed
672 if (frame->color_reloaded) {
673 LOG_DEBUG(Render_OpenGL, "Reloading present frame");
674 frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height);
675 }
676 glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED);
677 // INTEL workaround.
678 // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete
679 // it on the emulation thread without too much penalty
680 // glDeleteSync(frame.render_sync);
681 // frame.render_sync = 0;
682
683 glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle);
684 glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height,
685 GL_COLOR_BUFFER_BIT, GL_LINEAR);
686
687 // Insert fence for the main thread to block on
688 frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
689 glFlush();
690
691 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
692 return true;
693}
694
695void RendererOpenGL::RenderScreenshot() { 451void RendererOpenGL::RenderScreenshot() {
696 if (!renderer_settings.screenshot_requested) { 452 if (!renderer_settings.screenshot_requested) {
697 return; 453 return;
@@ -706,7 +462,7 @@ void RendererOpenGL::RenderScreenshot() {
706 screenshot_framebuffer.Create(); 462 screenshot_framebuffer.Create();
707 glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle); 463 glBindFramebuffer(GL_FRAMEBUFFER, screenshot_framebuffer.handle);
708 464
709 Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; 465 const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
710 466
711 GLuint renderbuffer; 467 GLuint renderbuffer;
712 glGenRenderbuffers(1, &renderbuffer); 468 glGenRenderbuffers(1, &renderbuffer);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 5329577fb..9ef181f95 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -55,14 +55,6 @@ struct ScreenInfo {
55 TextureInfo texture; 55 TextureInfo texture;
56}; 56};
57 57
58struct PresentationTexture {
59 u32 width = 0;
60 u32 height = 0;
61 OGLTexture texture;
62};
63
64class FrameMailbox;
65
66class RendererOpenGL final : public VideoCore::RendererBase { 58class RendererOpenGL final : public VideoCore::RendererBase {
67public: 59public:
68 explicit RendererOpenGL(Core::TelemetrySession& telemetry_session, 60 explicit RendererOpenGL(Core::TelemetrySession& telemetry_session,
@@ -74,7 +66,6 @@ public:
74 bool Init() override; 66 bool Init() override;
75 void ShutDown() override; 67 void ShutDown() override;
76 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; 68 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
77 bool TryPresent(int timeout_ms) override;
78 69
79private: 70private:
80 /// Initializes the OpenGL state and creates persistent objects. 71 /// Initializes the OpenGL state and creates persistent objects.
@@ -102,8 +93,6 @@ private:
102 93
103 void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer); 94 void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer);
104 95
105 bool Present(int timeout_ms);
106
107 Core::TelemetrySession& telemetry_session; 96 Core::TelemetrySession& telemetry_session;
108 Core::Frontend::EmuWindow& emu_window; 97 Core::Frontend::EmuWindow& emu_window;
109 Core::Memory::Memory& cpu_memory; 98 Core::Memory::Memory& cpu_memory;
@@ -134,11 +123,6 @@ private:
134 /// Used for transforming the framebuffer orientation 123 /// Used for transforming the framebuffer orientation
135 Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{}; 124 Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags{};
136 Common::Rectangle<int> framebuffer_crop_rect; 125 Common::Rectangle<int> framebuffer_crop_rect;
137
138 /// Frame presentation mailbox
139 std::unique_ptr<FrameMailbox> frame_mailbox;
140
141 bool has_debug_tool = false;
142}; 126};
143 127
144} // namespace OpenGL 128} // namespace OpenGL
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 0e4583986..d38e797a4 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -283,11 +283,6 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
283 render_window.PollEvents(); 283 render_window.PollEvents();
284} 284}
285 285
286bool RendererVulkan::TryPresent(int /*timeout_ms*/) {
287 // TODO (bunnei): ImplementMe
288 return true;
289}
290
291bool RendererVulkan::Init() { 286bool RendererVulkan::Init() {
292 library = OpenVulkanLibrary(); 287 library = OpenVulkanLibrary();
293 instance = CreateInstance(library, dld, render_window.GetWindowInfo().type, 288 instance = CreateInstance(library, dld, render_window.GetWindowInfo().type,
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index ddff77942..5085310d0 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -55,7 +55,6 @@ public:
55 bool Init() override; 55 bool Init() override;
56 void ShutDown() override; 56 void ShutDown() override;
57 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; 57 void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
58 bool TryPresent(int timeout_ms) override;
59 58
60 static std::vector<std::string> EnumerateDevices(); 59 static std::vector<std::string> EnumerateDevices();
61 60