Just a little renderer class I threw myself together over the past 2 days.
This class only has basic functionality as of right now and is far from complete but it should be easy enough to extend.
I will probably add some more neat features within in the next few weeks if I find the time to do so.
When using this always make sure to follow up every begin call with an end call or just use the provided RAII wrapper Renderer::Guard.
If you don't want to use the RAII wrapper for whatever reason (for example if you are a lazy soab for example and want to be able to initialize
via static Renderer renderer(device); you should probably remove the shared_from_this stuff.
In order to render efficiently always remember that begin()/end() locks/unlocks the vertex buffer and flushes it (draws the batched primitives) so
try to minimize the cpu cycles between begin/end pairs.
When you exceed the maximum number of vertices or want to draw a different primitive type flush() is called internally.
That's why you should always draw the same types of primitives at once if possible, of course.
Feel free to send me a message if you have any suggestions or spotted a nasty bug.
Basic Usage:
demo.cpp
Code:
#include "renderer.hpp"
std::shared_ptr<Renderer> renderer;
bool initRenderer = true;
// EndScene/Present/...
if (initRenderer)
{
renderer.reset(new Renderer(direct3DDevice));
initRenderer = false;
}
{
Renderer::Guard guard(renderer->ptr(), D3DPT_TRIANGLELIST);
renderer->drawOutlinedRect(Rect{ 450.f, 450.f, 150.f, 150.f }, 1.f, D3DCOLOR_ARGB(255, 0, 150, 150), D3DCOLOR_ARGB(100, 20, 20, 20));
}
renderer->setColor(D3DCOLOR_ARGB(255, 0, 150, 150));
{
Renderer::Guard guard(renderer->ptr(), D3DPT_LINELIST);
renderer->drawLine(D3DXVECTOR2{ 100.f, 100.f }, D3DXVECTOR2{ 300.f, 150.f });
}
{
Renderer::Guard guard(renderer->ptr(), D3DPT_LINESTRIP);
renderer->drawCircle(500.f, 200.f, 100.f);
}
// Reset
renderer->release();
HRESULT hr = originalReset(...);
if (SUCCEEDED(hr)) {
renderer->acquire();
}
renderer.hpp
Code:
#pragma once
#include <vector>
#include <d3d9.h>
#include <d3dx9.h>
#include <cassert>
#include <memory>
#include <sstream>
struct CustomVertex
{
float x, y, z, rhw;
D3DCOLOR color;
};
static const unsigned long CustomVertexFormat = D3DFVF_XYZRHW | D3DFVF_DIFFUSE;
struct TopologyType
{
D3DPRIMITIVETYPE m_type;
std::uint32_t m_div;
std::uint32_t m_sub;
};
static const TopologyType TopologyTypes [] =
{
{},
{ D3DPRIMITIVETYPE::D3DPT_POINTLIST , 1, 0 },
{ D3DPRIMITIVETYPE::D3DPT_LINELIST , 2, 0 },
{ D3DPRIMITIVETYPE::D3DPT_LINESTRIP , 1, 1 },
{ D3DPRIMITIVETYPE::D3DPT_TRIANGLELIST , 3, 0 },
{ D3DPRIMITIVETYPE::D3DPT_TRIANGLESTRIP , 1, 2 },
{ D3DPRIMITIVETYPE::D3DPT_TRIANGLEFAN , 1, 2 }
};
inline void ThrowIfFailed(HRESULT hr)
{
if (FAILED(hr))
{
std::stringstream sstream;
sstream << "HRESULT Code: " << std::hex << std::uppercase << hr;
throw std::exception(sstream.str().c_str()); // Set a breakpoint on this line to catch errors;
}
}
struct Rect
{
Rect()
: x(0.f), y(0.f), w(0.f), h(0.f)
{
}
Rect(float x, float y, float w, float h)
: x(x), y(y), w(w), h(h)
{
}
Rect(const Rect &other)
: x(other.x), y(other.y), w(other.w), h(other.h)
{
}
// TODO: finish
float x, y, w, h;
};
class Renderer
: public std::enable_shared_from_this<Renderer>
{
public:
Renderer(IDirect3DDevice9 *device = nullptr);
~Renderer();
void initialize(IDirect3DDevice9 *device);
void acquire();
void release();
void begin(D3DPRIMITIVETYPE topology = D3DPT_FORCE_DWORD);
void end();
void flush(D3DPRIMITIVETYPE topology = D3DPT_FORCE_DWORD);
void addVertex(float x, float y, D3DCOLOR color = 0, D3DPRIMITIVETYPE topology = D3DPT_FORCE_DWORD);
void drawFilledRect(float x, float y, float w, float h, D3DCOLOR color = 0);
void drawFilledRect(const Rect &rect, D3DCOLOR color = 0);
void drawRect(float x, float y, float w, float h, float strokeWidth = 1.f, D3DCOLOR color = 0);
void drawRect(const Rect &rect, float strokeWidth = 1.f, D3DCOLOR color = 0);
void drawOutlinedRect(float x, float y, float w, float h, float strokeWidth = 1.f, D3DCOLOR outlineColor = 0, D3DCOLOR rectColor = 0);
void drawOutlinedRect(const Rect &rect, float strokeWidth = 1.f, D3DCOLOR outlineColor = 0, D3DCOLOR rectColor = 0);
void drawLine(float x1, float y1, float x2, float y2, D3DCOLOR color = 0);
void drawLine(D3DXVECTOR2 src, D3DXVECTOR2 dst, D3DCOLOR color = 0);
void drawCircle(float x, float y, float r, D3DCOLOR color = 0);
void drawPixel(float x, float y, D3DCOLOR color = 0);
void setColor(D3DCOLOR color);
std::shared_ptr<Renderer> ptr();
/*
** Simple RAII wrapper for begin()/end() pairs
** Use it like this:
**
** {
** Renderer::Guard guard(renderer->ptr(), D3DPT_TRIANGLELIST);
** renderer->drawRect(Rect { 100.f, 100.f, 100.f, 100.f }, 1.f, D3DCOLOR_ARGB(100, 20, 20, 20));
** }
*/
class Guard
{
public:
Guard(const std::shared_ptr<Renderer> &renderer,
D3DPRIMITIVETYPE topology = D3DPT_FORCE_DWORD);
~Guard();
// non-copyable
Guard(const Guard &) = delete;
Guard& operator=(const Guard &) = delete;
private:
std::shared_ptr<Renderer> m_renderer;
};
private:
std::size_t m_vertexCount;
std::size_t m_maxVertices;
D3DPRIMITIVETYPE m_topology;
D3DCOLOR m_color;
IDirect3DDevice9 *m_device;
IDirect3DVertexBuffer9 *m_vertexBuffer;
CustomVertex *m_vertex;
HRESULT m_canRender;
};
renderer.cpp
Code:
#include "renderer.hpp"
Renderer::Renderer(IDirect3DDevice9 *device) :
m_device(device), m_vertexBuffer(nullptr), m_vertex(nullptr), m_maxVertices(1530), m_vertexCount(0), m_color(0xffffffff), m_canRender(E_FAIL)
{
assert(m_maxVertices % 3 == 0 && m_maxVertices % 2 == 0); // !! m_maxVertices must be divisible by 3 and 2 !!
//assert(m_device);
if (m_device)
acquire();
}
Renderer::~Renderer()
{
release();
}
void Renderer::initialize(IDirect3DDevice9 *device)
{
assert(m_device == nullptr);
m_device = device;
acquire();
}
void Renderer::acquire()
{
assert(m_device);
ThrowIfFailed(m_device->CreateVertexBuffer(m_maxVertices * sizeof(CustomVertex), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
CustomVertexFormat, D3DPOOL_DEFAULT, &m_vertexBuffer, nullptr));
}
void Renderer::release()
{
if (m_vertexBuffer)
{
m_vertexBuffer->Release();
m_vertexBuffer = nullptr;
}
}
void Renderer::begin(D3DPRIMITIVETYPE topology)
{
assert(m_vertexBuffer != nullptr);
ThrowIfFailed((m_canRender == S_OK) ? E_FAIL : S_OK);
m_topology = topology;
m_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
m_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
m_device->SetRenderState(D3DRS_LIGHTING, FALSE);
ThrowIfFailed(m_vertexBuffer->Lock(0, 0, reinterpret_cast<void **>(&m_vertex), D3DLOCK_DISCARD));
m_canRender = S_OK;
}
void Renderer::addVertex(float x, float y, D3DCOLOR color, D3DPRIMITIVETYPE topology)
{
assert(m_vertex != nullptr);
if (topology != D3DPT_FORCE_DWORD && m_topology != topology)
flush(topology);
if (m_vertexCount >= m_maxVertices)
flush();
if (!color)
color = m_color;
*m_vertex++ = { x, y, 0.f, 0.f, color };
m_vertexCount++;
}
void Renderer::drawFilledRect(const Rect &rect, D3DCOLOR color)
{
addVertex(rect.x, rect.y, color, D3DPT_TRIANGLELIST);
addVertex(rect.x + rect.w, rect.y, color);
addVertex(rect.x, rect.y + rect.h, color);
addVertex(rect.x + rect.w, rect.y, color);
addVertex(rect.x + rect.w, rect.y + rect.h, color);
addVertex(rect.x, rect.y + rect.h, color);
}
void Renderer::drawFilledRect(float x, float y, float w, float h, D3DCOLOR color)
{
drawFilledRect(Rect{ x, y, w, h }, color);
}
void Renderer::drawRect(const Rect &rect, float strokeWidth, D3DCOLOR color)
{
drawFilledRect(rect.x, rect.y, rect.w, strokeWidth, color);
drawFilledRect(rect.x, rect.y + rect.h - strokeWidth, rect.w, strokeWidth, color);
drawFilledRect(rect.x, rect.y, strokeWidth, rect.h, color);
drawFilledRect(rect.x + rect.w - strokeWidth, rect.y, strokeWidth, rect.h, color);
}
void Renderer::drawRect(float x, float y, float w, float h, float strokeWidth, D3DCOLOR color)
{
drawRect(Rect{ x, y, w, h }, strokeWidth, color);
}
void Renderer::drawOutlinedRect(float x, float y, float w, float h, float strokeWidth, D3DCOLOR outlineColor, D3DCOLOR rectColor)
{
drawOutlinedRect(Rect{ x,y,w,h }, strokeWidth, outlineColor, rectColor);
}
void Renderer::drawOutlinedRect(const Rect &rect, float strokeWidth, D3DCOLOR outlineColor, D3DCOLOR rectColor)
{
drawFilledRect(rect, rectColor);
drawRect(rect, strokeWidth, outlineColor);
}
void Renderer::drawLine(float x1, float y1, float x2, float y2, D3DCOLOR color)
{
addVertex(x1, y1, color, D3DPT_LINELIST);
addVertex(x2, y2, color);
}
void Renderer::drawLine(D3DXVECTOR2 src, D3DXVECTOR2 dst, D3DCOLOR color)
{
drawLine(src.x, src.y, dst.x, dst.y, color);
}
void Renderer::drawCircle(float x, float y, float r, D3DCOLOR color)
{
float segments = 10.f * std::sqrt(r); // change for better quality/performance
float theta = 2.f * 3.1415926f / segments;
float c = std::cos(theta);
float s = std::sin(theta);
float t;
float xx = r;
float yy = 0;
for (int i = 0; i < static_cast<int>(segments); ++i)
{
addVertex(xx + x, yy + y, color, D3DPT_LINESTRIP);
t = xx;
xx = c * xx - s * yy;
yy = s * t + c * yy;
}
addVertex(xx + x, yy + y, color);
flush();
}
void Renderer::drawPixel(float x, float y, D3DCOLOR color)
{
drawFilledRect(x, y, 1.f, 1.f, color);
}
void Renderer::setColor(D3DCOLOR color)
{
m_color = color;
}
std::shared_ptr<Renderer> Renderer::ptr() {
return shared_from_this();
}
void Renderer::end()
{
assert(m_vertexBuffer != nullptr);
ThrowIfFailed(m_canRender);
m_vertexBuffer->Unlock();
if (m_topology >= D3DPT_POINTLIST && m_topology <= D3DPT_TRIANGLEFAN) {
std::uint32_t primitiveCount = m_vertexCount / TopologyTypes[m_topology].m_div - TopologyTypes[m_topology].m_sub;
if (primitiveCount > 0)
{
m_device->SetFVF(CustomVertexFormat);
m_device->SetStreamSource(0, m_vertexBuffer, 0, sizeof(CustomVertex));
m_device->DrawPrimitive(m_topology, 0, primitiveCount);
m_vertexCount = 0;
}
}
m_canRender = E_FAIL;
}
void Renderer::flush(D3DPRIMITIVETYPE topology)
{
assert(m_vertexBuffer != nullptr);
end();
if (D3DPT_FORCE_DWORD == topology)
topology = m_topology;
begin(topology);
}
Renderer::Guard::Guard(const std::shared_ptr<Renderer> &renderer,
D3DPRIMITIVETYPE topology)
: m_renderer(renderer)
{
m_renderer->begin(topology);
}
Renderer::Guard::~Guard()
{
m_renderer->end();
}