Update src/RenderDevice.h

This commit is contained in:
2025-10-18 01:55:32 +00:00
parent 97543e130f
commit 78f1b2d7a9

View File

@@ -1,498 +1,495 @@
/*
RenderDevice — Inverse Interactive
Homepage: https://dock-it.dev/Inverse-Interactive/RenderDevice
License: Creative Commons Attribution-ShareAlike 4.0 International
SPDX-License-Identifier: CC-BY-SA-4.0
Full License Text: https://dock-it.dev/Inverse-Interactive/RenderDevice/raw/branch/main/LICENSE
Copyright (c) Inverse Interactive
You are free to share and adapt this work under the terms of CC BY-SA 4.0.
Provide appropriate credit and distribute derivatives under the same license.
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace PF
class RenderDevice
{
class RenderDevice
public:
struct Stats
{
public:
struct Stats
{
uint64_t drawCalls = 0;
uint64_t instancedDrawCalls = 0;
uint64_t verticesDrawn = 0;
uint64_t instancesDrawn = 0;
uint64_t indexedDrawCalls = 0;
uint64_t instancedIndexedDrawCalls = 0;
uint64_t elementsDrawn = 0;
uint64_t bufferUploads = 0;
uint64_t mappedBytes = 0;
uint64_t textureBinds = 0;
uint64_t vaoBinds = 0;
uint64_t bufferBinds = 0;
uint64_t stateChanges = 0;
uint64_t framebufferBinds = 0;
uint64_t blits = 0;
uint64_t mipmapGenerations = 0;
uint64_t samplerBinds = 0;
uint64_t pixelStoreChanges = 0;
uint64_t stencilStateChanges = 0;
uint64_t blendEquationChanges = 0;
uint64_t colorMaskChanges = 0;
uint64_t scissorChanges = 0;
uint64_t gpuTimeNsLastFrame = 0;
uint64_t gpuTimeNsAccum = 0;
uint32_t gpuTimersIssued = 0;
uint32_t gpuTimersResolved = 0;
uint32_t gpuTimersPending = 0;
void Reset() { *this = Stats{}; }
};
struct GPUInfo
{
std::string vendor;
std::string renderer;
std::string version;
std::string glslVersion;
int major = 0;
int minor = 0;
};
enum class DeviceErrors
{
OK,
glewInitFailed,
DeviceAlreadyCreated,
};
enum class ClearBits : uint32_t
{
None = 0,
Color = 1u << 0,
Depth = 1u << 1,
Stencil = 1u << 2
};
friend inline ClearBits operator|(ClearBits a, ClearBits b) { return ClearBits(uint32_t(a) | uint32_t(b)); }
friend inline bool operator&(uint32_t m, ClearBits b) { return (m & uint32_t(b)) != 0; }
enum class Compare
{
Never,
Less,
LEqual,
Equal,
GEqual,
Greater,
NotEqual,
Always
};
enum class CullFace
{
None,
Back,
Front,
FrontAndBack
};
enum class Winding
{
CCW,
CW
};
enum class BlendFactor
{
Zero,
One,
SrcAlpha,
OneMinusSrcAlpha,
SrcColor,
OneMinusSrcColor,
DstAlpha,
OneMinusDstAlpha,
DstColor,
OneMinusDstColor
};
enum class BlendEq : unsigned
{
Add = 0x8006,
Subtract = 0x800A,
ReverseSubtract = 0x800B,
Min = 0x8007,
Max = 0x8008
};
enum class DrawMode
{
Triangles,
Lines,
Points,
TriangleStrip,
LineStrip
};
enum class BufferTarget
{
Array,
Element
};
enum class Usage
{
StreamDraw,
StaticDraw,
DynamicDraw
};
enum class MapAccess : uint32_t
{
Write = 1 << 0,
InvalidateRange = 1 << 1,
Unsynchronized = 1 << 2,
InvalidateBuffer = 1 << 3
};
friend inline MapAccess operator|(MapAccess a, MapAccess b)
{
return static_cast<MapAccess>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b));
}
/* Giga: The constants are probably bad, but I don't want to include all of opengl headders in every rendering file
so this is just going to have to do.
And it probably will cause compatability problems.
*/
enum class PName : unsigned int
{
DrawFramebufferBinding = 0x8CA6,
ReadFramebufferBinding = 0x8CAA,
FramebufferBinding = 0x8CA6,
Viewport = 0x0BA2,
MaxTextureImageUnits = 0x8872,
MaxCombinedTextureImageUnits = 0x8B4D,
MajorVersion = 0x821B,
MinorVersion = 0x821C,
};
enum class AttribType
{
Float
};
enum class IndexType
{
U16,
U32
};
enum class TextureTarget : unsigned int
{
Texture2D = 0x0DE1,
TextureCubeMap = 0x8513,
Texture2DArray = 0x8C1A,
Texture3D = 0x806F,
TextureRectangle = 0x84F5
};
enum class InternalFormat : unsigned int
{
RGBA8 = 0x8058,
RGB8 = 0x8051,
RG8 = 0x822B,
R8 = 0x8229,
SRGB8_ALPHA8 = 0x8C43,
RGBA16F = 0x881A,
R16F = 0x822D,
DEPTH24_STENCIL8 = 0x88F0
};
enum class PixelFormat : unsigned int
{
RGBA = 0x1908,
RGB = 0x1907,
BGRA = 0x80E1,
BGR = 0x80E0,
RED = 0x1903,
DEPTH_COMPONENT = 0x1902,
DEPTH_STENCIL = 0x84F9
};
enum class PixelType : unsigned int
{
UnsignedByte = 0x1401,
Float = 0x1406,
UnsignedInt24_8 = 0x84FA
};
enum class TexFilter : int
{
Nearest = 0x2600,
Linear = 0x2601,
NearestMipmapNearest = 0x2700,
LinearMipmapNearest = 0x2701,
NearestMipmapLinear = 0x2702,
LinearMipmapLinear = 0x2703
};
enum class TexWrap : int
{
ClampToEdge = 0x812F,
Repeat = 0x2901,
MirroredRepeat = 0x8370,
ClampToBorder = 0x812D
};
struct Buffer
{
unsigned id = 0;
BufferTarget target = BufferTarget::Array;
size_t size = 0;
};
struct Texture
{
unsigned id = 0;
unsigned target = 0;
int w = 0, h = 0, levels = 1;
};
struct VAO
{
unsigned id = 0;
};
struct Framebuffer
{
unsigned id = 0;
};
struct Renderbuffer
{
unsigned id = 0;
};
struct Sampler
{
unsigned id = 0;
};
using GpuTimer = uint32_t;
void Init();
[[nodiscard]] const GPUInfo &GetGPUInfo() const { return m_gpu; }
void ResetStats()
{
m_stats.Reset();
}
[[nodiscard]] const Stats &GetStatsBack() const { return m_stats; }
[[nodiscard]] const Stats &GetStats() const { return m_stats; }
[[nodiscard]] Stats &GetStats() { return m_stats; }
void BeginGpuFrame();
void EndGpuFrame();
GpuTimer CreateGpuTimer();
void DestroyGpuTimer(GpuTimer h);
void BeginGpuTimer(GpuTimer h);
bool EndGpuTimer(GpuTimer h, uint64_t *nsOut, bool wait = false);
void Viewport(int x, int y, int w, int h);
void SetClearColor(float r, float g, float b, float a);
void Clear(uint32_t bitsMask);
void SetDepthTest(bool enabled);
void SetDepthFunc(Compare f);
void SetDepthMask(bool enableWrite);
void SetColorMask(bool r, bool g, bool b, bool a);
void SetCull(CullFace c);
void SetFrontFace(Winding w);
void SetBlend(bool enabled);
void SetBlendFunc(BlendFactor src, BlendFactor dst);
void SetBlendFuncSeparate(BlendFactor srcRGB, BlendFactor dstRGB, BlendFactor srcA, BlendFactor dstA);
void SetBlendEquation(BlendEq rgb, BlendEq a);
void SetStencil(bool enabled);
void SetStencilFunc(Compare func, int ref, unsigned mask);
void SetStencilOp(unsigned sfail, unsigned dpfail, unsigned dppass);
void SetStencilMask(unsigned mask);
void SetScissor(bool enabled, int x = 0, int y = 0, int w = 0, int h = 0);
VAO CreateVAO();
void BindVAO(const VAO &v);
void Destroy(VAO &v);
Buffer CreateBuffer(BufferTarget target, size_t sizeBytes, const void *data = nullptr,
Usage usage = Usage::DynamicDraw);
void BindBuffer(const Buffer &b);
void BufferData(Buffer &b, size_t newSize, const void *data, Usage usage);
void BufferSubData(const Buffer &b, size_t offset, size_t size, const void *data);
void *MapBufferRange(const Buffer &b, size_t offset, size_t size, MapAccess access);
void UnmapBuffer(const Buffer &b);
void Destroy(Buffer &b);
void EnableVertexAttrib(unsigned location);
void VertexAttribPointer(unsigned location, int compCount, AttribType type, bool normalized, int stride,
const void *offset);
void VertexAttribIPointer(unsigned location, int compCount, int stride, const void *offset);
void VertexAttribDivisor(unsigned location, unsigned divisor);
void GetIntegerv(PName pname, int *data) const;
int GetInteger(PName pname) const;
Texture CreateTexture2D(int w, int h, int levels = 1);
void UploadTexture2D(const Texture &t, int level, int x, int y, int w, int h, const void *pixels,
unsigned format, unsigned type);
void SetTexParams2D(const Texture &t, int minFilter, int magFilter, int wrapS, int wrapT);
void BindTextureUnit(unsigned unit, const Texture &t);
void GenerateMipmap(const Texture &t);
void PixelStorei(unsigned pname, int param);
Sampler CreateSampler();
void SetSamplerParams(Sampler s, TexFilter minFilter, TexFilter magFilter, TexWrap wrapS, TexWrap wrapT);
void SetSamplerAnisotropy(Sampler s, float aniso);
void BindSampler(unsigned unit, Sampler s);
void Destroy(Sampler &s);
void Destroy(Texture &t);
Framebuffer CreateFramebuffer();
void BindFramebuffer(const Framebuffer &fb);
void BindDefaultFramebuffer();
void AttachColorTexture2D(const Framebuffer &fb, const Texture &tex, int attachmentIndex = 0);
Renderbuffer CreateDepthStencilRBO(int w, int h);
void AttachDepthStencil(const Framebuffer &fb, const Renderbuffer &rbo);
bool CheckFramebufferComplete(const Framebuffer &fb);
void Destroy(Framebuffer &fb);
void Destroy(Renderbuffer &rb);
void BlitFramebuffer(const Framebuffer &src, const Framebuffer &dst,
int sx0, int sy0, int sx1, int sy1,
int dx0, int dy0, int dx1, int dy1,
bool color = true, bool depth = false, bool stencil = false,
bool linear = true);
void DrawArrays(DrawMode mode, int first, int count);
void DrawArraysInstanced(DrawMode mode, int first, int count, int instanceCount);
void DrawElements(DrawMode mode, int count, IndexType type, const void *offset);
void DrawElementsInstanced(DrawMode mode, int count, IndexType type, const void *offset, int instanceCount);
void DrawElementsBaseVertex(DrawMode mode, int count, IndexType type, const void *offset, int baseVertex);
void SetObjectLabel(unsigned glType, unsigned id, const std::string &name);
Texture CreateTexture2D(int w, int h, int levels, InternalFormat internalFmt);
void UploadTexture2D(const Texture &t, int level, int x, int y, int w, int h, const void *pixels,
PixelFormat fmt, PixelType type);
void SetTexParams2D(const Texture &t, TexFilter minFilter, TexFilter magFilter, TexWrap wrapS, TexWrap wrapT);
void UnbindTextureUnit(unsigned unit);
static RenderDevice *GetDevice() { return m_activeDevice; }
DeviceErrors GetLastError() const { return m_lastError; }
private:
void _refreshGPUInfo();
inline static RenderDevice *m_activeDevice = nullptr;
int m_viewport[4]{0, 0, 0, 0};
float m_clear[4]{0, 0, 0, 1};
bool m_depthTest = false;
int m_depthFunc = 0;
int m_cullFace = 0;
int m_frontFace = 0;
bool m_blend = false;
int m_blendSrc = 0, m_blendDst = 0;
bool m_depthMask = true;
bool m_colorMask[4]{true, true, true, true};
bool m_scissorEnabled = false;
int m_scissorBox[4]{0, 0, 0, 0};
bool m_stencilEnabled = false;
unsigned m_stencilMask = 0xFF;
unsigned m_blendEqRGB = 0x8006; // GL_FUNC_ADD
unsigned m_blendEqA = 0x8006;
unsigned m_boundVAO = 0;
unsigned m_boundArrayBuffer = 0;
unsigned m_boundElementBuffer = 0;
unsigned m_boundTexUnits[32]{};
unsigned m_boundSamplers[32]{};
unsigned m_boundFramebuffer = 0;
unsigned m_frameTimerQuery = 0;
bool m_frameTimerActive = false;
struct TimerSlot
{
unsigned id = 0;
bool active = false;
};
std::vector<TimerSlot> m_timerPool;
Stats m_stats;
GPUInfo m_gpu{};
DeviceErrors m_lastError = DeviceErrors::OK;
uint64_t drawCalls = 0;
uint64_t instancedDrawCalls = 0;
uint64_t verticesDrawn = 0;
uint64_t instancesDrawn = 0;
uint64_t indexedDrawCalls = 0;
uint64_t instancedIndexedDrawCalls = 0;
uint64_t elementsDrawn = 0;
uint64_t bufferUploads = 0;
uint64_t mappedBytes = 0;
uint64_t textureBinds = 0;
uint64_t vaoBinds = 0;
uint64_t bufferBinds = 0;
uint64_t stateChanges = 0;
uint64_t framebufferBinds = 0;
uint64_t blits = 0;
uint64_t mipmapGenerations = 0;
uint64_t samplerBinds = 0;
uint64_t pixelStoreChanges = 0;
uint64_t stencilStateChanges = 0;
uint64_t blendEquationChanges = 0;
uint64_t colorMaskChanges = 0;
uint64_t scissorChanges = 0;
uint64_t gpuTimeNsLastFrame = 0;
uint64_t gpuTimeNsAccum = 0;
uint32_t gpuTimersIssued = 0;
uint32_t gpuTimersResolved = 0;
uint32_t gpuTimersPending = 0;
void Reset() { *this = Stats{}; }
};
}
struct GPUInfo
{
std::string vendor;
std::string renderer;
std::string version;
std::string glslVersion;
int major = 0;
int minor = 0;
};
enum class DeviceErrors
{
OK,
glewInitFailed,
DeviceAlreadyCreated,
};
enum class ClearBits : uint32_t
{
None = 0,
Color = 1u << 0,
Depth = 1u << 1,
Stencil = 1u << 2
};
friend inline ClearBits operator|(ClearBits a, ClearBits b) { return ClearBits(uint32_t(a) | uint32_t(b)); }
friend inline bool operator&(uint32_t m, ClearBits b) { return (m & uint32_t(b)) != 0; }
enum class Compare
{
Never,
Less,
LEqual,
Equal,
GEqual,
Greater,
NotEqual,
Always
};
enum class CullFace
{
None,
Back,
Front,
FrontAndBack
};
enum class Winding
{
CCW,
CW
};
enum class BlendFactor
{
Zero,
One,
SrcAlpha,
OneMinusSrcAlpha,
SrcColor,
OneMinusSrcColor,
DstAlpha,
OneMinusDstAlpha,
DstColor,
OneMinusDstColor
};
enum class BlendEq : unsigned
{
Add = 0x8006,
Subtract = 0x800A,
ReverseSubtract = 0x800B,
Min = 0x8007,
Max = 0x8008
};
enum class DrawMode
{
Triangles,
Lines,
Points,
TriangleStrip,
LineStrip
};
enum class BufferTarget
{
Array,
Element
};
enum class Usage
{
StreamDraw,
StaticDraw,
DynamicDraw
};
enum class MapAccess : uint32_t
{
Write = 1 << 0,
InvalidateRange = 1 << 1,
Unsynchronized = 1 << 2,
InvalidateBuffer = 1 << 3
};
friend inline MapAccess operator|(MapAccess a, MapAccess b)
{
return static_cast<MapAccess>(static_cast<uint32_t>(a) | static_cast<uint32_t>(b));
}
/* Giga: The constants are probably bad, but I don't want to include all of opengl headders in every rendering file
so this is just going to have to do.
And it probably will cause compatability problems.
*/
enum class PName : unsigned int
{
DrawFramebufferBinding = 0x8CA6,
ReadFramebufferBinding = 0x8CAA,
FramebufferBinding = 0x8CA6,
Viewport = 0x0BA2,
MaxTextureImageUnits = 0x8872,
MaxCombinedTextureImageUnits = 0x8B4D,
MajorVersion = 0x821B,
MinorVersion = 0x821C,
};
enum class AttribType
{
Float
};
enum class IndexType
{
U16,
U32
};
enum class TextureTarget : unsigned int
{
Texture2D = 0x0DE1,
TextureCubeMap = 0x8513,
Texture2DArray = 0x8C1A,
Texture3D = 0x806F,
TextureRectangle = 0x84F5
};
enum class InternalFormat : unsigned int
{
RGBA8 = 0x8058,
RGB8 = 0x8051,
RG8 = 0x822B,
R8 = 0x8229,
SRGB8_ALPHA8 = 0x8C43,
RGBA16F = 0x881A,
R16F = 0x822D,
DEPTH24_STENCIL8 = 0x88F0
};
enum class PixelFormat : unsigned int
{
RGBA = 0x1908,
RGB = 0x1907,
BGRA = 0x80E1,
BGR = 0x80E0,
RED = 0x1903,
DEPTH_COMPONENT = 0x1902,
DEPTH_STENCIL = 0x84F9
};
enum class PixelType : unsigned int
{
UnsignedByte = 0x1401,
Float = 0x1406,
UnsignedInt24_8 = 0x84FA
};
enum class TexFilter : int
{
Nearest = 0x2600,
Linear = 0x2601,
NearestMipmapNearest = 0x2700,
LinearMipmapNearest = 0x2701,
NearestMipmapLinear = 0x2702,
LinearMipmapLinear = 0x2703
};
enum class TexWrap : int
{
ClampToEdge = 0x812F,
Repeat = 0x2901,
MirroredRepeat = 0x8370,
ClampToBorder = 0x812D
};
struct Buffer
{
unsigned id = 0;
BufferTarget target = BufferTarget::Array;
size_t size = 0;
};
struct Texture
{
unsigned id = 0;
unsigned target = 0;
int w = 0, h = 0, levels = 1;
};
struct VAO
{
unsigned id = 0;
};
struct Framebuffer
{
unsigned id = 0;
};
struct Renderbuffer
{
unsigned id = 0;
};
struct Sampler
{
unsigned id = 0;
};
using GpuTimer = uint32_t;
void Init();
[[nodiscard]] const GPUInfo &GetGPUInfo() const { return m_gpu; }
void ResetStats()
{
m_stats.Reset();
}
[[nodiscard]] const Stats &GetStatsBack() const { return m_stats; }
[[nodiscard]] const Stats &GetStats() const { return m_stats; }
[[nodiscard]] Stats &GetStats() { return m_stats; }
void BeginGpuFrame();
void EndGpuFrame();
GpuTimer CreateGpuTimer();
void DestroyGpuTimer(GpuTimer h);
void BeginGpuTimer(GpuTimer h);
bool EndGpuTimer(GpuTimer h, uint64_t *nsOut, bool wait = false);
void Viewport(int x, int y, int w, int h);
void SetClearColor(float r, float g, float b, float a);
void Clear(uint32_t bitsMask);
void SetDepthTest(bool enabled);
void SetDepthFunc(Compare f);
void SetDepthMask(bool enableWrite);
void SetColorMask(bool r, bool g, bool b, bool a);
void SetCull(CullFace c);
void SetFrontFace(Winding w);
void SetBlend(bool enabled);
void SetBlendFunc(BlendFactor src, BlendFactor dst);
void SetBlendFuncSeparate(BlendFactor srcRGB, BlendFactor dstRGB, BlendFactor srcA, BlendFactor dstA);
void SetBlendEquation(BlendEq rgb, BlendEq a);
void SetStencil(bool enabled);
void SetStencilFunc(Compare func, int ref, unsigned mask);
void SetStencilOp(unsigned sfail, unsigned dCXail, unsigned dppass);
void SetStencilMask(unsigned mask);
void SetScissor(bool enabled, int x = 0, int y = 0, int w = 0, int h = 0);
VAO CreateVAO();
void BindVAO(const VAO &v);
void Destroy(VAO &v);
Buffer CreateBuffer(BufferTarget target, size_t sizeBytes, const void *data = nullptr,
Usage usage = Usage::DynamicDraw);
void BindBuffer(const Buffer &b);
void BufferData(Buffer &b, size_t newSize, const void *data, Usage usage);
void BufferSubData(const Buffer &b, size_t offset, size_t size, const void *data);
void *MapBufferRange(const Buffer &b, size_t offset, size_t size, MapAccess access);
void UnmapBuffer(const Buffer &b);
void Destroy(Buffer &b);
void EnableVertexAttrib(unsigned location);
void VertexAttribPointer(unsigned location, int compCount, AttribType type, bool normalized, int stride,
const void *offset);
void VertexAttribIPointer(unsigned location, int compCount, int stride, const void *offset);
void VertexAttribDivisor(unsigned location, unsigned divisor);
void GetIntegerv(PName pname, int *data) const;
int GetInteger(PName pname) const;
Texture CreateTexture2D(int w, int h, int levels = 1);
void UploadTexture2D(const Texture &t, int level, int x, int y, int w, int h, const void *pixels,
unsigned format, unsigned type);
void SetTexParams2D(const Texture &t, int minFilter, int magFilter, int wrapS, int wrapT);
void BindTextureUnit(unsigned unit, const Texture &t);
void GenerateMipmap(const Texture &t);
void PixelStorei(unsigned pname, int param);
Sampler CreateSampler();
void SetSamplerParams(Sampler s, TexFilter minFilter, TexFilter magFilter, TexWrap wrapS, TexWrap wrapT);
void SetSamplerAnisotropy(Sampler s, float aniso);
void BindSampler(unsigned unit, Sampler s);
void Destroy(Sampler &s);
void Destroy(Texture &t);
Framebuffer CreateFramebuffer();
void BindFramebuffer(const Framebuffer &fb);
void BindDefaultFramebuffer();
void AttachColorTexture2D(const Framebuffer &fb, const Texture &tex, int attachmentIndex = 0);
Renderbuffer CreateDepthStencilRBO(int w, int h);
void AttachDepthStencil(const Framebuffer &fb, const Renderbuffer &rbo);
bool CheckFramebufferComplete(const Framebuffer &fb);
void Destroy(Framebuffer &fb);
void Destroy(Renderbuffer &rb);
void BlitFramebuffer(const Framebuffer &src, const Framebuffer &dst,
int sx0, int sy0, int sx1, int sy1,
int dx0, int dy0, int dx1, int dy1,
bool color = true, bool depth = false, bool stencil = false,
bool linear = true);
void DrawArrays(DrawMode mode, int first, int count);
void DrawArraysInstanced(DrawMode mode, int first, int count, int instanceCount);
void DrawElements(DrawMode mode, int count, IndexType type, const void *offset);
void DrawElementsInstanced(DrawMode mode, int count, IndexType type, const void *offset, int instanceCount);
void DrawElementsBaseVertex(DrawMode mode, int count, IndexType type, const void *offset, int baseVertex);
void SetObjectLabel(unsigned glType, unsigned id, const std::string &name);
Texture CreateTexture2D(int w, int h, int levels, InternalFormat internalFmt);
void UploadTexture2D(const Texture &t, int level, int x, int y, int w, int h, const void *pixels,
PixelFormat fmt, PixelType type);
void SetTexParams2D(const Texture &t, TexFilter minFilter, TexFilter magFilter, TexWrap wrapS, TexWrap wrapT);
void UnbindTextureUnit(unsigned unit);
static RenderDevice *GetDevice() { return m_activeDevice; }
DeviceErrors GetLastError() const { return m_lastError; }
private:
void _refreshGPUInfo();
inline static RenderDevice *m_activeDevice = nullptr;
int m_viewport[4]{0, 0, 0, 0};
float m_clear[4]{0, 0, 0, 1};
bool m_depthTest = false;
int m_depthFunc = 0;
int m_cullFace = 0;
int m_frontFace = 0;
bool m_blend = false;
int m_blendSrc = 0, m_blendDst = 0;
bool m_depthMask = true;
bool m_colorMask[4]{true, true, true, true};
bool m_scissorEnabled = false;
int m_scissorBox[4]{0, 0, 0, 0};
bool m_stencilEnabled = false;
unsigned m_stencilMask = 0xFF;
unsigned m_blendEqRGB = 0x8006; // GL_FUNC_ADD
unsigned m_blendEqA = 0x8006;
unsigned m_boundVAO = 0;
unsigned m_boundArrayBuffer = 0;
unsigned m_boundElementBuffer = 0;
unsigned m_boundTexUnits[32]{};
unsigned m_boundSamplers[32]{};
unsigned m_boundFramebuffer = 0;
unsigned m_frameTimerQuery = 0;
bool m_frameTimerActive = false;
struct TimerSlot
{
unsigned id = 0;
bool active = false;
};
std::vector<TimerSlot> m_timerPool;
Stats m_stats;
GPUInfo m_gpu{};
DeviceErrors m_lastError = DeviceErrors::OK;
};