Compare commits

..

8 Commits

Author SHA1 Message Date
26a392b1d0 example - shared ptr
All checks were successful
Build / Build (push) Successful in 26m42s
2026-04-14 18:56:40 -04:00
b90c980bee ctx - device
Some checks failed
Build / Build (push) Has been cancelled
2026-04-14 18:49:35 -04:00
f5c4ac0790 ctx - max sample usage 2026-04-14 18:20:52 -04:00
bd6c4523dd ctx - physical device
All checks were successful
Build / Build (push) Successful in 3m14s
2026-04-13 17:41:56 -04:00
7d50f6c148 Surface
All checks were successful
Build / Build (push) Successful in 3m15s
2026-04-13 17:08:31 -04:00
aa39f81bb1 examples - bug fix in window.h
All checks were successful
Build / Build (push) Successful in 2m58s
2026-04-12 23:47:37 -04:00
14b4e60d3a examples - comments
Some checks failed
Build / Build (push) Failing after 2m8s
2026-04-12 23:42:15 -04:00
fd35e93831 examples - rework shared into header only 2026-04-12 23:26:50 -04:00
14 changed files with 366 additions and 87 deletions

View File

@@ -1,33 +1,55 @@
// basicGLFWWindow
// While I've got a basic initWindow() function in shared/window.h, I mostly just wanted to give an example of how to
// create a window with glfw without it. You can always figure out from the window.h file as well, but it is nice and
// easy to just look here instead, as you're probably already here anyways.
// Oatmeal does ship with GLFW linked as public, so you should be able to just link with it in your CMakeLists.txt file
// without having to add it yourself.
// - Firewire
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include "GLFW/glfw3.h" #include "GLFW/glfw3.h"
#include "shared/logger.h" #include "shared/logger.h"
int main() { int main() {
// Init and create the logger
OatmealUtils::initLogging(); OatmealUtils::initLogging();
OatmealUtils::createLogger("window", nullptr); OatmealUtils::createLogger("window", nullptr);
OatmealUtils::get("window")->info("Initializing GLFW"); // Initialize the GLFW backend
OatmealUtils::getLogger("window")->info("Initializing GLFW");
glfwInit(); glfwInit();
OatmealUtils::get("window")->info("Setting window hints"); // GLFW works on hints for configuration.
// Since oatmeal is entirely vulkan based and GLFW creates an opengl context by default, we need to tell it not to.
// At the time of writing (very very early indev) there is no resizable window support, so thats turned off here as
// well.
OatmealUtils::getLogger("window")->info("Setting window hints");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
OatmealUtils::get("window")->info("Create window"); // Create the window, running a small error check just in case it failed for whatever reason; and if it did, grab
// the error from glfw and print it out.
OatmealUtils::getLogger("window")->info("Create window");
GLFWwindow *window = glfwCreateWindow(800, 600, "Oatmeal - Basic GLFW window", nullptr, nullptr); GLFWwindow *window = glfwCreateWindow(800, 600, "Oatmeal - Basic GLFW window", nullptr, nullptr);
if (window == nullptr) { if (window == nullptr) {
const char *desc; const char *desc;
uint32_t code = glfwGetError(&desc); uint32_t code = glfwGetError(&desc);
OatmealUtils::get("window")->critical("Failed to create window: ({}) {}", code, desc); OatmealUtils::getLogger("window")->critical("Failed to create window: ({}) {}", code, desc);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// This here is whats known as the main application loop. Everything with graphics/games/whatever runs in a loop,
// updating values as needed before it starts over again. The time it takes to run that loop is known at the
// frametime, and how many times you can run that loop per second is the FPS (sorta)
// Since we are just creating a window and have no way of updating it yet, we can just poll for any events and
// continue.
while (!glfwWindowShouldClose(window)) { while (!glfwWindowShouldClose(window)) {
glfwPollEvents(); glfwPollEvents();
} }
OatmealUtils::get("window")->info("Cleaning up"); // Cleanup the window and terminate the glfw backend
OatmealUtils::getLogger("window")->info("Cleaning up");
glfwDestroyWindow(window); glfwDestroyWindow(window);
glfwTerminate(); glfwTerminate();

View File

@@ -1,45 +1,43 @@
#include <cstdint> // createContext
// Still in progress, although I'm 95% sure this wont get changed.
// Come back later!
// - Firewire
#include <cstdlib> #include <cstdlib>
#include <exception> #include <exception>
#include <memory>
#include "GLFW/glfw3.h" #include "GLFW/glfw3.h"
#include "shared/logger.h" #include "shared/logger.h"
#include "shared/window.h"
#include "ctx.h" #include "ctx.h"
int main() { int main() {
OatmealUtils::initLogging(); OatmealUtils::initLogging();
OatmealUtils::createLogger("window", nullptr);
OatmealUtils::createLogger("context", nullptr); OatmealUtils::createLogger("context", nullptr);
OatmealUtils::get("window")->info("Initializing GLFW"); GLFWwindow *window = OatmealUtils::initWindow("Oatmeal - createContext", 800, 600);
glfwInit();
OatmealUtils::get("window")->info("Setting window hints");
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
OatmealUtils::get("window")->info("Create window"); std::shared_ptr<Oatmeal::ctx> ctx = nullptr;
GLFWwindow *window = glfwCreateWindow(800, 600, "Oatmeal - Basic GLFW window", nullptr, nullptr);
if (window == nullptr) {
const char *desc;
uint32_t code = glfwGetError(&desc);
OatmealUtils::get("window")->critical("Failed to create window: ({}) {}", code, desc);
exit(EXIT_FAILURE);
}
OatmealUtils::getLogger("context")->info("Creating context");
try { try {
Oatmeal::ctx ctx(window); ctx = std::make_shared<Oatmeal::ctx>(window);
} catch (const std::exception &e) { } catch (const std::exception &e) {
OatmealUtils::get("context")->critical("{}", e.what()); OatmealUtils::getLogger("context")->critical("{}", e.what());
return EXIT_FAILURE; return EXIT_FAILURE;
} }
OatmealUtils::getLogger("context")->info("Device name: {}", ctx->getDeviceName());
OatmealUtils::getLogger("context")->info("Device Type: {}", ctx->getDeviceType());
OatmealUtils::getLogger("context")->info("Starting main loop");
while (!glfwWindowShouldClose(window)) { while (!glfwWindowShouldClose(window)) {
glfwPollEvents(); glfwPollEvents();
} }
OatmealUtils::get("window")->info("Cleaning up"); OatmealUtils::getLogger("window")->info("Cleaning up");
glfwDestroyWindow(window); glfwDestroyWindow(window);
glfwTerminate(); glfwTerminate();

View File

@@ -1,3 +1,9 @@
// Logging
// This program is just to test out/show any new features I add to the shared/logging.h file.
// It is mostly a wrapper around spdlog, nothing fancy at all.
// The code should be self explanatory enough so I'm not going to bother commenting everything out.
// - Firewire
#include <cstdint> #include <cstdint>
#include "shared/logger.h" #include "shared/logger.h"
@@ -5,13 +11,13 @@ int main() {
OatmealUtils::initLogging(); OatmealUtils::initLogging();
OatmealUtils::createLogger("logger", nullptr); OatmealUtils::createLogger("logger", nullptr);
OatmealUtils::get("logger")->debug("This is a debug message!"); OatmealUtils::getLogger("logger")->debug("This is a debug message!");
OatmealUtils::get("logger")->info("This is an info message!"); OatmealUtils::getLogger("logger")->info("This is an info message!");
OatmealUtils::get("logger")->warn("This is a warning message!"); OatmealUtils::getLogger("logger")->warn("This is a warning message!");
OatmealUtils::get("logger")->critical("This is a critical message!"); OatmealUtils::getLogger("logger")->critical("This is a critical message!");
uint32_t a = 5; uint32_t a = 5;
std::string b = "Test string arg!"; std::string b = "Test string arg!";
OatmealUtils::get("logger")->info("String arg: {} uint32_t args: {}", b, a); OatmealUtils::getLogger("logger")->info("String arg: {} uint32_t args: {}", b, a);
return 0; return 0;
} }

View File

@@ -1,48 +0,0 @@
#include "logger.h"
#include <memory>
#include <spdlog/async_logger.h>
#include <spdlog/common.h>
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/spdlog.h"
namespace OatmealUtils {
void initLogging() {
spdlog::init_thread_pool(8192, 1);
spdlog::flush_every(std::chrono::seconds(1));
}
void createLogger(const char *name, const char *filename) {
std::vector<spdlog::sink_ptr> sinks{};
const auto stdoutSink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
stdoutSink->set_level(spdlog::level::debug);
stdoutSink->set_pattern("%^[%D %r %z] [%n] [%l] [thread %t] %v%$");
sinks.emplace_back(stdoutSink);
if (filename != nullptr) {
const auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename);
fileSink->set_level(spdlog::level::debug);
fileSink->set_pattern("%^[%D %r %z] [%n] [%l] [thread %t] %v%$");
sinks.emplace_back(fileSink);
}
const auto logger = std::make_shared<spdlog::async_logger>(
name, sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
logger->flush_on(spdlog::level::warn);
#ifndef NDEBUG
logger->set_level(spdlog::level::debug);
#else
logger->set_level(spdlog::level::info);
#endif // !NDEBUG
spdlog::register_logger(logger);
}
void createLogger(std::string name, std::string filename) { createLogger(name.c_str(), filename.c_str()); }
std::shared_ptr<logger> get(const char *name) { return spdlog::get(name); }
std::shared_ptr<logger> get(std::string name) { return spdlog::get(name); }
} // namespace OatmealUtils

View File

@@ -1,20 +1,57 @@
#ifndef OATMEAL_LOGGER #ifndef OATMEAL_UTILS_LOGGER
#define OATMEAL_LOGGER #define OATMEAL_UTILS_LOGGER
#include <memory> #include <memory>
#include <spdlog/async_logger.h>
#include <spdlog/common.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/spdlog.h"
#include "string" #include "string"
namespace OatmealUtils { namespace OatmealUtils {
using logger = spdlog::logger; using logger = spdlog::logger;
void initLogging(); inline void initLogging() {
void createLogger(const char *name, const char *filename); spdlog::init_thread_pool(8192, 1);
void createLogger(std::string name, std::string filename); spdlog::flush_every(std::chrono::seconds(1));
std::shared_ptr<logger> get(const char *name); }
std::shared_ptr<logger> get(std::string name);
inline void createLogger(const char *name, const char *filename) {
std::vector<spdlog::sink_ptr> sinks{};
const auto stdoutSink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
stdoutSink->set_level(spdlog::level::debug);
stdoutSink->set_pattern("%^[%D %r %z] [%n] [%l] [thread %t] %v%$");
sinks.emplace_back(stdoutSink);
if (filename != nullptr) {
const auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename);
fileSink->set_level(spdlog::level::debug);
fileSink->set_pattern("%^[%D %r %z] [%n] [%l] [thread %t] %v%$");
sinks.emplace_back(fileSink);
}
const auto logger = std::make_shared<spdlog::async_logger>(
name, sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
logger->flush_on(spdlog::level::warn);
#ifndef NDEBUG
logger->set_level(spdlog::level::debug);
#else
logger->set_level(spdlog::level::info);
#endif // !NDEBUG
spdlog::register_logger(logger);
}
inline void createLogger(std::string name, std::string filename) { createLogger(name.c_str(), filename.c_str()); }
inline std::shared_ptr<logger> getLogger(const char *name) { return spdlog::get(name); }
inline std::shared_ptr<logger> getLogger(std::string name) { return spdlog::get(name); }
} // namespace OatmealUtils } // namespace OatmealUtils
#endif // !OATMEAL_LOGGER #endif // !OATMEAL_UTILS_LOGGER

View File

@@ -0,0 +1,24 @@
#include <cstdint>
#include <cstdlib>
#include "GLFW/glfw3.h"
#include "shared/logger.h"
namespace OatmealUtils {
inline GLFWwindow *initWindow(const char *title, uint32_t width, uint32_t height) {
OatmealUtils::createLogger("window", nullptr);
OatmealUtils::getLogger("window")->debug("Initializing window backend");
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
OatmealUtils::getLogger("window")->debug("Creating window");
GLFWwindow *window = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (window == nullptr) {
const char *desc;
uint32_t code = glfwGetError(&desc);
OatmealUtils::getLogger("window")->critical("Failed to create window: ({}) {}", code, desc);
exit(EXIT_FAILURE);
}
return window;
}
} // namespace OatmealUtils

29
examples/shared/window.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef OATMEAL_UTILS_WINDOW
#define OATMEAL_UTILS_WINDOW
#include <cstdint>
#include "GLFW/glfw3.h"
#include "shared/logger.h"
namespace OatmealUtils {
inline GLFWwindow *initWindow(const char *title, uint32_t width, uint32_t height) {
OatmealUtils::initLogging();
OatmealUtils::createLogger("window", nullptr);
OatmealUtils::get("window")->debug("Initializing window backend");
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
OatmealUtils::get("window")->debug("Creating window");
GLFWwindow *window = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (window == nullptr) {
const char *desc;
uint32_t code = glfwGetError(&desc);
OatmealUtils::get("window")->critical("Failed to create window: ({}) {}", code, desc);
exit(EXIT_FAILURE);
}
return window;
}
} // namespace OatmealUtils
#endif // !OATMEAL_UTILS_WINDOW

View File

@@ -0,0 +1,64 @@
#include <stdexcept>
#include "ctx.h"
#include "vulkan/vulkan.hpp"
#include "vulkan/vulkan_raii.hpp"
namespace Oatmeal {
void ctx::createLogicalDevice() {
std::vector<vk::QueueFamilyProperties> queueFamilyProperties = m_physicalDevice.getQueueFamilyProperties();
m_queueIndex = ~0;
for (uint32_t qfpIndex = 0; qfpIndex < queueFamilyProperties.size(); qfpIndex++) {
if ((queueFamilyProperties[qfpIndex].queueFlags & vk::QueueFlagBits::eGraphics) &&
m_physicalDevice.getSurfaceSupportKHR(qfpIndex, *m_surface)) {
m_queueIndex = qfpIndex;
break;
}
}
if (m_queueIndex == ~0) {
// TODO: Find a better solution then just throwing a fit
throw std::runtime_error("Failed to find a queue that supports graphics and presentation");
}
vk::PhysicalDeviceFeatures deviceFeatures;
vk::StructureChain<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features,
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>
featureChain = {
{.features = {.samplerAnisotropy = true}},
{.synchronization2 = true, .dynamicRendering = true},
{.extendedDynamicState = true},
};
float queuePriority = 0.5f;
vk::DeviceQueueCreateInfo deviceQueueCreateInfo = {
.queueFamilyIndex = m_queueIndex,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
vk::DeviceCreateInfo deviceCreateInfo = {
.pNext = &featureChain.get<vk::PhysicalDeviceFeatures2>(),
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &deviceQueueCreateInfo,
.enabledExtensionCount = static_cast<uint32_t>(requiredDeviceExtensions.size()),
.ppEnabledExtensionNames = requiredDeviceExtensions.data(),
};
m_device = vk::raii::Device(m_physicalDevice, deviceCreateInfo);
m_queue = vk::raii::Queue(m_device, m_queueIndex, 0);
}
std::string ctx::getDeviceName() const {
const vk::PhysicalDeviceProperties deviceProperties = m_physicalDevice.getProperties();
return deviceProperties.deviceName;
}
std::string ctx::getDeviceType() const {
const vk::PhysicalDeviceProperties deviceProperties = m_physicalDevice.getProperties();
return vk::to_string(deviceProperties.deviceType);
}
} // namespace Oatmeal

View File

@@ -1,8 +1,14 @@
#include "ctx.h" #include "ctx.h"
#include <cstdint>
namespace Oatmeal { namespace Oatmeal {
ctx::ctx(GLFWwindow *window) { createInstance(); } ctx::ctx(GLFWwindow *window) {
createInstance();
m_window = window;
createSurface();
pickPhysicalDevice();
m_msaaSamples = getMaxUsableSampleCount();
createLogicalDevice();
}
ctx::~ctx() {} ctx::~ctx() {}

View File

@@ -41,11 +41,17 @@ namespace Oatmeal {
class ctx { class ctx {
public: public:
ctx(GLFWwindow *window); ctx(GLFWwindow *window);
std::string getDeviceName() const;
std::string getDeviceType() const;
~ctx(); ~ctx();
private: private:
// Members // // Members //
GLFWwindow *m_window = nullptr;
vk::raii::Context m_context; vk::raii::Context m_context;
vk::raii::Instance m_instance = nullptr; vk::raii::Instance m_instance = nullptr;
vk::DebugUtilsMessengerEXT m_debugMessenger = nullptr; vk::DebugUtilsMessengerEXT m_debugMessenger = nullptr;
@@ -81,5 +87,14 @@ namespace Oatmeal {
void createInstance(); void createInstance();
std::vector<const char *> getRequiredExtensions(); std::vector<const char *> getRequiredExtensions();
void setupDebugMessenger(); void setupDebugMessenger();
void createSurface();
void pickPhysicalDevice();
bool isPhysicalDeviceSupported(vk::raii::PhysicalDevice device);
uint32_t scorePhysicalDevice(vk::raii::PhysicalDevice device);
vk::SampleCountFlagBits getMaxUsableSampleCount();
void createLogicalDevice();
}; };
} // namespace Oatmeal } // namespace Oatmeal

View File

@@ -0,0 +1,27 @@
#include "ctx.h"
#include "vulkan/vulkan.hpp"
namespace Oatmeal {
vk::SampleCountFlagBits ctx::getMaxUsableSampleCount() {
vk::PhysicalDeviceProperties physicalDeviceProperties = m_physicalDevice.getProperties();
vk::SampleCountFlags count = physicalDeviceProperties.limits.framebufferColorSampleCounts &
physicalDeviceProperties.limits.framebufferDepthSampleCounts;
if (count & vk::SampleCountFlagBits::e16) {
return vk::SampleCountFlagBits::e16;
}
if (count & vk::SampleCountFlagBits::e8) {
return vk::SampleCountFlagBits::e8;
}
if (count & vk::SampleCountFlagBits::e4) {
return vk::SampleCountFlagBits::e4;
}
if (count & vk::SampleCountFlagBits::e2) {
return vk::SampleCountFlagBits::e2;
}
return vk::SampleCountFlagBits::e1;
}
} // namespace Oatmeal

View File

@@ -1,5 +1,4 @@
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <X11/Xlib.h>
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>

View File

@@ -0,0 +1,83 @@
#include <algorithm>
#include <cstdint>
#include <map>
#include <stdexcept>
#include <utility>
#include <vulkan/vulkan_core.h>
#include <vulkan/vulkan_raii.hpp>
#include "ctx.h"
#include "vulkan/vulkan.hpp"
namespace Oatmeal {
void ctx::pickPhysicalDevice() {
std::vector<vk::raii::PhysicalDevice> physicalDevices = m_instance.enumeratePhysicalDevices();
std::vector<vk::raii::PhysicalDevice> supportedDevices{};
for (const auto device: physicalDevices) {
if (isPhysicalDeviceSupported(device)) {
supportedDevices.emplace_back(device);
}
}
if (supportedDevices.empty()) {
throw std::runtime_error("Failed to find a supported device");
}
std::multimap<uint32_t, vk::raii::PhysicalDevice> scores;
for (const auto device: supportedDevices) {
uint32_t score = scorePhysicalDevice(device);
scores.insert(std::make_pair(score, device));
}
m_physicalDevice = scores.rbegin()->second;
}
bool ctx::isPhysicalDeviceSupported(vk::raii::PhysicalDevice device) {
// Check for vulkan 1.4 support
bool supportsVulkan14 = device.getProperties().apiVersion >= VK_API_VERSION_1_4;
// Check for graphics queue support
auto queueFamilies = device.getQueueFamilyProperties();
bool supportsGraphics = std::ranges::any_of(
queueFamilies, [](auto const &qfp) { return !!(qfp.queueFlags & vk::QueueFlagBits::eGraphics); });
// Check for extension support
auto availableDeviceExtensions = device.enumerateDeviceExtensionProperties();
bool supportsAllRequiredExtensions = std::ranges::all_of(
requiredDeviceExtensions, [&availableDeviceExtensions](auto const &requiredDeviceExtension) {
return std::ranges::any_of(
availableDeviceExtensions, [requiredDeviceExtension](auto const &availableDeviceExtension) {
return strcmp(availableDeviceExtension.extensionName, requiredDeviceExtension) == 0;
});
});
// Check for feature support
auto features = device.template getFeatures2<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan13Features,
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>();
bool supportsRequiredFeatures =
features.template get<vk::PhysicalDeviceFeatures2>().features.samplerAnisotropy &&
features.template get<vk::PhysicalDeviceVulkan13Features>().dynamicRendering &&
features.template get<vk::PhysicalDeviceVulkan13Features>().synchronization2 &&
features.template get<vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>().extendedDynamicState;
return supportsVulkan14 && supportsGraphics && supportsAllRequiredExtensions && supportsRequiredFeatures;
}
uint32_t ctx::scorePhysicalDevice(vk::raii::PhysicalDevice device) {
uint32_t score = 0;
vk::PhysicalDeviceProperties physicalDeviceProperties = device.getProperties();
if (physicalDeviceProperties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) {
score += 100;
} else if (physicalDeviceProperties.deviceType == vk::PhysicalDeviceType::eIntegratedGpu) {
score += 50;
} else if (physicalDeviceProperties.deviceType == vk::PhysicalDeviceType::eVirtualGpu) {
score += 10;
}
return score;
}
} // namespace Oatmeal

View File

@@ -0,0 +1,17 @@
#include "ctx.h"
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <vulkan/vulkan_core.h>
#include <vulkan/vulkan_raii.hpp>
namespace Oatmeal {
void ctx::createSurface() {
VkSurfaceKHR _surface;
if (glfwCreateWindowSurface(*m_instance, m_window, nullptr, &_surface)) {
throw std::runtime_error("Failed to create window surface");
}
m_surface = vk::raii::SurfaceKHR(m_instance, _surface);
}
} // namespace Oatmeal