Jump to content
  • Advertisement
Sign in to follow this  
Gage64

OpenGL Efficient immediate-mode rendering in D3D (source included)

This topic is 3792 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I wrote a class that allows you to efficiently render primitives using an OpenGL-like immediate-mode interface in D3D. It uses a dynamic vertex buffer that is rendered in batches. Here's the source:
#ifndef VERTEXCACHE_H
#define VERTEXCACHE_H


#include <d3d9.h>

#include <vector>
#include <exception>
#include <cstdlib>
#include <cassert>


struct TexCoords {
	float u, v;
};

struct Vertex {
	float x, y, z;
	float nx, ny, nz;
	DWORD diffuse;
	TexCoords texCoords[2];

	enum { FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX2 };
};

struct VertexT {
	float x, y, z;
	float rhw;
	DWORD diffuse;
	TexCoords texCoords[2];

	enum { FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX2 };
};


template <class V>
class VertexCacheBase {
public:
	VertexCacheBase(IDirect3DDevice9 *dev, int vbSz, int batchSz);
	~VertexCacheBase() { free(); }

	void free();

	void begin(D3DPRIMITIVETYPE primType) {
		primitiveType = primType;
		
		if (primitiveType == D3DPT_LINESTRIP ||
			primitiveType == D3DPT_TRIANGLESTRIP ||
			primitiveType == D3DPT_TRIANGLEFAN)
			strip = true;
		else
			strip = false;

		vbActualBatchSize = adjustBatchSize(primitiveType, vbBatchSize);
	}

	void end() {
		// Render last batch, if there is one
		if (verts.size() == 0)
			return;

		// For strips, use entire vertex buffer. See note in vertex().
		// (An alternative would have been to start a new batch, but then
		// the strip size can't be larger than one batch)
		if (strip) {
			vbActualBatchSize = vbSize;		// Will be re-adjusted in the next call to begin()
			vbBatchIndex = 0;				// Will be re-adjusted inside renderBatch()
		}

		renderBatch();
	}
	
	void color(float r, float g, float b, float a = 1.0f) {
		currVertex.diffuse = D3DCOLOR_COLORVALUE(r, g, b, a);
	}

	void color(int r, int g, int b, int a = 255) {
		currVertex.diffuse = D3DCOLOR_RGBA(r, g, b, a);
	}

	void texCoords(int unit, float u, float v) {
		assert(unit > 0);
		assert(unit < 2 && "Only 2 texture units are supported");

		currVertex.texCoords[unit].u = u;
		currVertex.texCoords[unit].v = v;
	}

	void texCoords(float u, float v) {
		texCoords(0, u, v);
	}

protected:
	void addVertex() {
		verts.push_back(currVertex);
		
		// Strip primitives cannot be broken into batches correctly, so always render them in one
		// batch (inside end())
		if (!strip)
			if (verts.size() == vbActualBatchSize)
				renderBatch();
	}

	V currVertex;

private:
	// Disallow copy/assignment
	VertexCacheBase(const VertexCacheBase &);
	void operator=(const VertexCacheBase &);

	void renderBatch();

	int adjustBatchSize(D3DPRIMITIVETYPE primType, int batchSize) {
		// For simplicity, make the batch size at least 3
		if (batchSize < 3)
			batchSize = 3;
		
		switch (primType) {
			case D3DPT_POINTLIST:
				// No adjustment needed
				break;

			case D3DPT_LINELIST:
				makeMultipleOf(&batchSize, 2);
				break;
			
			case D3DPT_TRIANGLELIST:
				makeMultipleOf(&batchSize, 3);
				break;

			// For strip primitives, the batch size will be adjusted in end() so
			// do nothing (the return value is ignored)
		}

		return batchSize;
	}
	
	int calcPrimitiveCount(D3DPRIMITIVETYPE primType, int batchSize) {
		switch (primType) {
			case D3DPT_POINTLIST:
				return batchSize;
			
			case D3DPT_LINELIST:
				return batchSize / 2;

			case D3DPT_LINESTRIP:
				return batchSize - 1;
			
			case D3DPT_TRIANGLELIST:
				return batchSize / 3;

			case D3DPT_TRIANGLESTRIP:
			case D3DPT_TRIANGLEFAN:
				return batchSize - 2;
		}

		return 0;
	}

	void makeMultipleOf(int *val, int mul) {
		int diff = *val % mul;
		*val -= diff;
	}
	
	IDirect3DDevice9 *device;
	IDirect3DVertexBuffer9 *vb;
	D3DPRIMITIVETYPE primitiveType;

	std::vector<V> verts;
	
	int vbSize;
	const int vbBatchSize;
	int vbActualBatchSize;
	int vbBatchIndex;

	bool strip;
};


template <class T>
void safeRelease(T *&p) {
	if (p) {
		p->Release();
		p = NULL;
	}
}


template <class V>
VertexCacheBase<V>::VertexCacheBase(IDirect3DDevice9 *dev, int vbSz, int batchSz)
: device(dev), vbSize(vbSz), vbBatchSize(batchSz), vbActualBatchSize(batchSz), vbBatchIndex(0), vb(NULL),
  primitiveType(), strip(false)
{
	assert(device);
	assert(vbSize >= vbBatchSize);

	makeMultipleOf(&vbSize, vbBatchSize);

	verts.reserve(vbBatchSize);

	device->AddRef();

	HRESULT hr = device->CreateVertexBuffer(sizeof(V) * vbSize, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 0,
											D3DPOOL_DEFAULT, &vb, NULL);
	if (FAILED(hr)) {
		free();
		throw std::runtime_error("VertexCacheBase() - Failed to create vertex buffer");
	}
}


template <class V>
void VertexCacheBase<V>::free() {
	safeRelease(vb);
	safeRelease(device);
}


template <class V>
void VertexCacheBase<V>::renderBatch() {
	device->SetStreamSource(0, vb, 0, sizeof(V));
	device->SetFVF(V::FVF);
	
	void *v;
	HRESULT hr = vb->Lock(vbBatchIndex * sizeof(V),
			 (DWORD)verts.size() * sizeof(V),
			 &v,
			 vbBatchIndex ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD);
	if (FAILED(hr))
		throw std::runtime_error("VertexCacheBase::renderBatch() - Failed to lock vertex buffer");

	memcpy(v, &verts[0], verts.size() * sizeof(V));
	vb->Unlock();

	int primCount = calcPrimitiveCount(primitiveType, (int)verts.size());
	hr = device->DrawPrimitive(primitiveType, vbBatchIndex, primCount);
	if (FAILED(hr))
		throw std::runtime_error("VertexCacheBase::renderBatch() - DrawPrimitive() failed");

	// Prepare for next batch
	vbBatchIndex += vbActualBatchSize;
	if (vbBatchIndex >= vbSize)
		vbBatchIndex = 0;

	// Because of batch size adjustment, there might be a small space left at the end of the vertex
	// buffer that is smaller than a batch (probably much smaller). If there is, discard it.
	if (vbSize - vbBatchIndex < vbActualBatchSize)
		vbBatchIndex = 0;

	verts.clear();
}


class VertexCache : public VertexCacheBase<Vertex> {
public:
	VertexCache(IDirect3DDevice9 *dev, int vbSz, int batchSz)
	: VertexCacheBase<Vertex>(dev, vbSz, batchSz) {}

	void vertex(float x, float y, float z = 0.0f) {
		currVertex.x = x;
		currVertex.y = y;
		currVertex.z = z;
		addVertex();
	}

	void normal(float nx, float ny, float nz) {
		currVertex.nx = nx;
		currVertex.ny = ny;
		currVertex.nz = nz;
	}
};


class VertexCacheT : public VertexCacheBase<VertexT> {
public:
	VertexCacheT(IDirect3DDevice9 *dev, int vbSz, int batchSz)
		: VertexCacheBase(dev, vbSz, batchSz) {}

	void vertex(float x, float y, float z = 0.0f, float rhw = 1.0f) {
		currVertex.x = x;
		currVertex.y = y;
		currVertex.z = z;
		currVertex.rhw = rhw;
		addVertex();
	}
};



#endif	// VERTEXCACHE_H

The code was slightly inspired by this article, but I tried to make it more efficient (plus I really wanted to be able to use screen coordinates). The class turned out to be more complicated than I wanted, because I wanted to properly support "strip" primitives (line strip, triangle strip and triangle fan), and these cannot be split into batches. Also, I wanted to support both transformed and untransformed coordinates, and because of the latter I was forced to create two separate classes (maybe there's a way to avoid this). It's probably possible to make improvements and additions to this class, but I wanted to hear what people think about it so far. I'm posting the code here in the hope that someone will find it useful, and to hear any comments you might have.

Share this post


Link to post
Share on other sites
Advertisement
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!