summaryrefslogtreecommitdiffstats
path: root/src/video_core
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core')
-rw-r--r--src/video_core/command_processor.cpp37
-rw-r--r--src/video_core/engines/fermi_2d.cpp61
-rw-r--r--src/video_core/engines/fermi_2d.h89
-rw-r--r--src/video_core/engines/maxwell_3d.cpp24
-rw-r--r--src/video_core/engines/maxwell_3d.h21
-rw-r--r--src/video_core/gpu.cpp14
-rw-r--r--src/video_core/gpu.h10
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp6
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp10
-rw-r--r--src/video_core/textures/decoders.cpp5
-rw-r--r--src/video_core/textures/decoders.h4
-rw-r--r--src/video_core/video_core.cpp6
12 files changed, 218 insertions, 69 deletions
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index 2c04daba3..2eaece298 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -24,41 +24,18 @@ namespace Tegra {
enum class BufferMethods {
BindObject = 0,
- SetGraphMacroCode = 0x45,
- SetGraphMacroCodeArg = 0x46,
- SetGraphMacroEntry = 0x47,
- CountBufferMethods = 0x100,
+ CountBufferMethods = 0x40,
};
void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params) {
- LOG_WARNING(HW_GPU, "Processing method %08X on subchannel %u value %08X remaining params %u",
- method, subchannel, value, remaining_params);
-
- if (method == static_cast<u32>(BufferMethods::SetGraphMacroEntry)) {
- // Prepare to upload a new macro, reset the upload counter.
- LOG_DEBUG(HW_GPU, "Uploading GPU macro %08X", value);
- current_macro_entry = value;
- current_macro_code.clear();
- return;
- }
-
- if (method == static_cast<u32>(BufferMethods::SetGraphMacroCodeArg)) {
- // Append a new code word to the current macro.
- current_macro_code.push_back(value);
-
- // There are no more params remaining, submit the code to the 3D engine.
- if (remaining_params == 0) {
- maxwell_3d->SubmitMacroCode(current_macro_entry, std::move(current_macro_code));
- current_macro_entry = InvalidGraphMacroEntry;
- current_macro_code.clear();
- }
-
- return;
- }
+ NGLOG_WARNING(HW_GPU,
+ "Processing method {:08X} on subchannel {} value "
+ "{:08X} remaining params {}",
+ method, subchannel, value, remaining_params);
if (method == static_cast<u32>(BufferMethods::BindObject)) {
// Bind the current subchannel to the desired engine id.
- LOG_DEBUG(HW_GPU, "Binding subchannel %u to engine %u", subchannel, value);
+ NGLOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value);
ASSERT(bound_engines.find(subchannel) == bound_engines.end());
bound_engines[subchannel] = static_cast<EngineID>(value);
return;
@@ -66,7 +43,7 @@ void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params)
if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) {
// TODO(Subv): Research and implement these methods.
- LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented");
+ NGLOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented");
return;
}
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index 7aab163dc..9019f2504 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -2,12 +2,71 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/memory.h"
#include "video_core/engines/fermi_2d.h"
+#include "video_core/textures/decoders.h"
namespace Tegra {
namespace Engines {
-void Fermi2D::WriteReg(u32 method, u32 value) {}
+Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
+
+void Fermi2D::WriteReg(u32 method, u32 value) {
+ ASSERT_MSG(method < Regs::NUM_REGS,
+ "Invalid Fermi2D register, increase the size of the Regs structure");
+
+ regs.reg_array[method] = value;
+
+ switch (method) {
+ case FERMI2D_REG_INDEX(trigger): {
+ HandleSurfaceCopy();
+ break;
+ }
+ }
+}
+
+void Fermi2D::HandleSurfaceCopy() {
+ NGLOG_WARNING(HW_GPU, "Requested a surface copy with operation {}",
+ static_cast<u32>(regs.operation));
+
+ const GPUVAddr source = regs.src.Address();
+ const GPUVAddr dest = regs.dst.Address();
+
+ // TODO(Subv): Only same-format and same-size copies are allowed for now.
+ ASSERT(regs.src.format == regs.dst.format);
+ ASSERT(regs.src.width * regs.src.height == regs.dst.width * regs.dst.height);
+
+ // TODO(Subv): Only raw copies are implemented.
+ ASSERT(regs.operation == Regs::Operation::SrcCopy);
+
+ const VAddr source_cpu = *memory_manager.GpuToCpuAddress(source);
+ const VAddr dest_cpu = *memory_manager.GpuToCpuAddress(dest);
+
+ u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format);
+ u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format);
+
+ if (regs.src.linear == regs.dst.linear) {
+ // If the input layout and the output layout are the same, just perform a raw copy.
+ Memory::CopyBlock(dest_cpu, source_cpu,
+ src_bytes_per_pixel * regs.dst.width * regs.dst.height);
+ return;
+ }
+
+ u8* src_buffer = Memory::GetPointer(source_cpu);
+ u8* dst_buffer = Memory::GetPointer(dest_cpu);
+
+ if (!regs.src.linear && regs.dst.linear) {
+ // If the input is tiled and the output is linear, deswizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
+ dst_bytes_per_pixel, src_buffer, dst_buffer, true,
+ regs.src.block_height);
+ } else {
+ // If the input is linear and the output is tiled, swizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
+ dst_bytes_per_pixel, dst_buffer, src_buffer, false,
+ regs.dst.block_height);
+ }
+}
} // namespace Engines
} // namespace Tegra
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 8967ddede..0c5b413cc 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -4,19 +4,106 @@
#pragma once
+#include <array>
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
namespace Tegra {
namespace Engines {
+#define FERMI2D_REG_INDEX(field_name) \
+ (offsetof(Tegra::Engines::Fermi2D::Regs, field_name) / sizeof(u32))
+
class Fermi2D final {
public:
- Fermi2D() = default;
+ explicit Fermi2D(MemoryManager& memory_manager);
~Fermi2D() = default;
/// Write the value to the register identified by method.
void WriteReg(u32 method, u32 value);
+
+ struct Regs {
+ static constexpr size_t NUM_REGS = 0x258;
+
+ struct Surface {
+ RenderTargetFormat format;
+ BitField<0, 1, u32> linear;
+ union {
+ BitField<0, 4, u32> block_depth;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_width;
+ };
+ u32 depth;
+ u32 layer;
+ u32 pitch;
+ u32 width;
+ u32 height;
+ u32 address_high;
+ u32 address_low;
+
+ GPUVAddr Address() const {
+ return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
+ address_low);
+ }
+ };
+ static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");
+
+ enum class Operation : u32 {
+ SrcCopyAnd = 0,
+ ROPAnd = 1,
+ Blend = 2,
+ SrcCopy = 3,
+ ROP = 4,
+ SrcCopyPremult = 5,
+ BlendPremult = 6,
+ };
+
+ union {
+ struct {
+ INSERT_PADDING_WORDS(0x80);
+
+ Surface dst;
+
+ INSERT_PADDING_WORDS(2);
+
+ Surface src;
+
+ INSERT_PADDING_WORDS(0x15);
+
+ Operation operation;
+
+ INSERT_PADDING_WORDS(0x9);
+
+ // TODO(Subv): This is only a guess.
+ u32 trigger;
+
+ INSERT_PADDING_WORDS(0x1A3);
+ };
+ std::array<u32, NUM_REGS> reg_array;
+ };
+ } regs{};
+
+ MemoryManager& memory_manager;
+
+private:
+ /// Performs the copy from the source surface to the destination surface as configured in the
+ /// registers.
+ void HandleSurfaceCopy();
};
+#define ASSERT_REG_POSITION(field_name, position) \
+ static_assert(offsetof(Fermi2D::Regs, field_name) == position * 4, \
+ "Field " #field_name " has invalid position")
+
+ASSERT_REG_POSITION(dst, 0x80);
+ASSERT_REG_POSITION(src, 0x8C);
+ASSERT_REG_POSITION(operation, 0xAB);
+ASSERT_REG_POSITION(trigger, 0xB5);
+#undef ASSERT_REG_POSITION
+
} // namespace Engines
} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 4e9aed380..4306b894f 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -22,10 +22,6 @@ constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(MemoryManager& memory_manager)
: memory_manager(memory_manager), macro_interpreter(*this) {}
-void Maxwell3D::SubmitMacroCode(u32 entry, std::vector<u32> code) {
- uploaded_macros[entry * 2 + MacroRegistersStart] = std::move(code);
-}
-
void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
auto macro_code = uploaded_macros.find(method);
// The requested macro must have been uploaded already.
@@ -37,9 +33,6 @@ void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
}
void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
- ASSERT_MSG(method < Regs::NUM_REGS,
- "Invalid Maxwell3D register, increase the size of the Regs structure");
-
auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
// It is an error to write to a register other than the current macro's ARG register before it
@@ -68,6 +61,9 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
return;
}
+ ASSERT_MSG(method < Regs::NUM_REGS,
+ "Invalid Maxwell3D register, increase the size of the Regs structure");
+
if (debug_context) {
debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandLoaded, nullptr);
}
@@ -75,6 +71,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
regs.reg_array[method] = value;
switch (method) {
+ case MAXWELL3D_REG_INDEX(macros.data): {
+ ProcessMacroUpload(value);
+ break;
+ }
case MAXWELL3D_REG_INDEX(code_address.code_address_high):
case MAXWELL3D_REG_INDEX(code_address.code_address_low): {
// Note: For some reason games (like Puyo Puyo Tetris) seem to write 0 to the CODE_ADDRESS
@@ -141,6 +141,12 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
}
}
+void Maxwell3D::ProcessMacroUpload(u32 data) {
+ // Store the uploaded macro code to interpret them when they're called.
+ auto& macro = uploaded_macros[regs.macros.entry * 2 + MacroRegistersStart];
+ macro.push_back(data);
+}
+
void Maxwell3D::ProcessQueryGet() {
GPUVAddr sequence_address = regs.query.QueryAddress();
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
@@ -186,8 +192,8 @@ void Maxwell3D::ProcessQueryGet() {
}
void Maxwell3D::DrawArrays() {
- LOG_DEBUG(HW_GPU, "called, topology=%d, count=%d", regs.draw.topology.Value(),
- regs.vertex_buffer.count);
+ NGLOG_DEBUG(HW_GPU, "called, topology={}, count={}",
+ static_cast<u32>(regs.draw.topology.Value()), regs.vertex_buffer.count);
ASSERT_MSG(!(regs.index_array.count && regs.vertex_buffer.count), "Both indexed and direct?");
auto debug_context = Core::System::GetInstance().GetGPUDebugContext();
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index a022665eb..5cf62fb01 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -31,7 +31,7 @@ public:
/// Register structure of the Maxwell3D engine.
/// TODO(Subv): This structure will need to be made bigger as more registers are discovered.
struct Regs {
- static constexpr size_t NUM_REGS = 0xE36;
+ static constexpr size_t NUM_REGS = 0xE00;
static constexpr size_t NumRenderTargets = 8;
static constexpr size_t NumViewports = 16;
@@ -322,7 +322,15 @@ public:
union {
struct {
- INSERT_PADDING_WORDS(0x200);
+ INSERT_PADDING_WORDS(0x45);
+
+ struct {
+ INSERT_PADDING_WORDS(1);
+ u32 data;
+ u32 entry;
+ } macros;
+
+ INSERT_PADDING_WORDS(0x1B8);
struct {
u32 address_high;
@@ -605,7 +613,7 @@ public:
u32 size[MaxShaderStage];
} tex_info_buffers;
- INSERT_PADDING_WORDS(0x102);
+ INSERT_PADDING_WORDS(0xCC);
};
std::array<u32, NUM_REGS> reg_array;
};
@@ -637,9 +645,6 @@ public:
/// Write the value to the register identified by method.
void WriteReg(u32 method, u32 value, u32 remaining_params);
- /// Uploads the code for a GPU macro program associated with the specified entry.
- void SubmitMacroCode(u32 entry, std::vector<u32> code);
-
/// Returns a list of enabled textures for the specified shader stage.
std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const;
@@ -670,6 +675,9 @@ private:
*/
void CallMacroMethod(u32 method, std::vector<u32> parameters);
+ /// Handles writes to the macro uploading registers.
+ void ProcessMacroUpload(u32 data);
+
/// Handles a write to the QUERY_GET register.
void ProcessQueryGet();
@@ -687,6 +695,7 @@ private:
static_assert(offsetof(Maxwell3D::Regs, field_name) == position * 4, \
"Field " #field_name " has invalid position")
+ASSERT_REG_POSITION(macros, 0x45);
ASSERT_REG_POSITION(rt, 0x200);
ASSERT_REG_POSITION(viewport_transform[0], 0x280);
ASSERT_REG_POSITION(viewport, 0x300);
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 9463cd5d6..9eb143918 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -12,7 +12,7 @@ namespace Tegra {
GPU::GPU() {
memory_manager = std::make_unique<MemoryManager>();
maxwell_3d = std::make_unique<Engines::Maxwell3D>(*memory_manager);
- fermi_2d = std::make_unique<Engines::Fermi2D>();
+ fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
}
@@ -22,4 +22,16 @@ const Tegra::Engines::Maxwell3D& GPU::Get3DEngine() const {
return *maxwell_3d;
}
+u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
+ ASSERT(format != RenderTargetFormat::NONE);
+
+ switch (format) {
+ case RenderTargetFormat::RGBA8_UNORM:
+ case RenderTargetFormat::RGB10_A2_UNORM:
+ return 4;
+ default:
+ UNIMPLEMENTED_MSG("Unimplemented render target format %u", static_cast<u32>(format));
+ }
+}
+
} // namespace Tegra
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 2888daedc..f168a5171 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -21,6 +21,9 @@ enum class RenderTargetFormat : u32 {
RGBA8_SRGB = 0xD6,
};
+/// Returns the number of bytes per pixel of each rendertarget format.
+u32 RenderTargetBytesPerPixel(RenderTargetFormat format);
+
class DebugContext;
/**
@@ -86,8 +89,6 @@ public:
}
private:
- static constexpr u32 InvalidGraphMacroEntry = 0xFFFFFFFF;
-
/// Writes a single register in the engine bound to the specified subchannel
void WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params);
@@ -100,11 +101,6 @@ private:
std::unique_ptr<Engines::Fermi2D> fermi_2d;
/// Compute engine
std::unique_ptr<Engines::MaxwellCompute> maxwell_compute;
-
- /// Entry of the macro that is currently being uploaded
- u32 current_macro_entry = InvalidGraphMacroEntry;
- /// Code being uploaded for the current macro
- std::vector<u32> current_macro_code;
};
} // namespace Tegra
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index b457b1fbe..9b3542e10 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -116,7 +116,7 @@ RasterizerOpenGL::RasterizerOpenGL() {
glEnable(GL_BLEND);
- LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
+ NGLOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
}
RasterizerOpenGL::~RasterizerOpenGL() {
@@ -252,8 +252,8 @@ void RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
break;
}
default:
- LOG_CRITICAL(HW_GPU, "Unimplemented shader index=%d, enable=%d, offset=0x%08X", index,
- shader_config.enable.Value(), shader_config.offset);
+ NGLOG_CRITICAL(HW_GPU, "Unimplemented shader index={}, enable={}, offset={:#010X}",
+ index, shader_config.enable.Value(), shader_config.offset);
UNREACHABLE();
}
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 5ca9821b7..77d1692f4 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -302,8 +302,8 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
right = texcoords.left;
} else {
// Other transformations are unsupported
- LOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags=%d",
- framebuffer_transform_flags);
+ NGLOG_CRITICAL(Render_OpenGL, "Unsupported framebuffer_transform_flags={}",
+ static_cast<u32>(framebuffer_transform_flags));
UNIMPLEMENTED();
}
}
@@ -428,9 +428,9 @@ bool RendererOpenGL::Init() {
const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
- LOG_INFO(Render_OpenGL, "GL_VERSION: %s", gl_version);
- LOG_INFO(Render_OpenGL, "GL_VENDOR: %s", gpu_vendor);
- LOG_INFO(Render_OpenGL, "GL_RENDERER: %s", gpu_model);
+ NGLOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
+ NGLOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
+ NGLOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor);
Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model);
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 9c3ae875c..8b39b2bdf 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -27,9 +27,8 @@ static u32 GetSwizzleOffset(u32 x, u32 y, u32 image_width, u32 bytes_per_pixel,
return address;
}
-static void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
- u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
- u32 block_height) {
+void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
+ u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) {
u8* data_ptrs[2];
for (unsigned y = 0; y < height; ++y) {
for (unsigned x = 0; x < width; ++x) {
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index a700911cf..2562c4b06 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -17,6 +17,10 @@ namespace Texture {
std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width, u32 height,
u32 block_height = TICEntry::DefaultBlockHeight);
+/// Copies texture data from a buffer and performs swizzling/unswizzling as necessary.
+void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
+ u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height);
+
/**
* Decodes an unswizzled texture into a A8R8G8B8 texture.
*/
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 289140f31..89dc8ed1e 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -24,9 +24,9 @@ bool Init(EmuWindow* emu_window) {
g_renderer = std::make_unique<RendererOpenGL>();
g_renderer->SetWindow(g_emu_window);
if (g_renderer->Init()) {
- LOG_DEBUG(Render, "initialized OK");
+ NGLOG_DEBUG(Render, "initialized OK");
} else {
- LOG_CRITICAL(Render, "initialization failed !");
+ NGLOG_CRITICAL(Render, "initialization failed !");
return false;
}
return true;
@@ -36,7 +36,7 @@ bool Init(EmuWindow* emu_window) {
void Shutdown() {
g_renderer.reset();
- LOG_DEBUG(Render, "shutdown OK");
+ NGLOG_DEBUG(Render, "shutdown OK");
}
} // namespace VideoCore