SlimDX: Strange behavior on VertexBuffer/IndexBuffer locks

Started by
6 comments, last by Promethium 16 years, 6 months ago
I've some strange behavior when using dynamic buffers to display quad's. I follow the usual steps: Create dynamic buffers, lock, fill, unlock, draw, but the quads I was drawing was flickering madly. After going over all of my code I ended up in the buffer locking. I used the following to lock:

mVertexBuffer.Lock(offset * mStride, lf)

This seemed fine, but then I saw in the SlimDX code that I could also pass a size along to Lock. So I changed the code to

mVertexBuffer.Lock(offset * mStride, count * mStride, lf)

and suddenly all the flickering stopped, and the quad's was rendered perfectly. In SlimDX Lock is as follows:

DataStream^ VertexBuffer::Lock( int offset, int size, LockFlags flags )
{
	void* lockedPtr;
	HRESULT hr = VbPointer->Lock( offset, size, &lockedPtr, (DWORD) flags );
	GraphicsException::CheckHResult( hr );
		
	int lockedSize = size == 0 ? SizeInBytes : size;
		
	bool readOnly = (flags & LockFlags::ReadOnly) == LockFlags::ReadOnly;
	DataStream^ stream = gcnew DataStream( lockedPtr, lockedSize, true, !readOnly );
	return stream;
}
	
DataStream^ VertexBuffer::Lock( int offset, LockFlags flags )
{
	return Lock( offset, 0, flags );
}

but after looking in the DX documenation I found the following under IDirect3DVertexBuffer9::Lock:
Quote: OffsetToLock [in] Offset into the vertex data to lock, in bytes. To lock the entire vertex buffer, specify 0 for both parameters, SizeToLock and OffsetToLock. SizeToLock [in] Size of the vertex data to lock, in bytes. To lock the entire vertex buffer, specify 0 for both parameters, SizeToLock and OffsetToLock.
It seems having OffsetToLock != 0 and SizeToLock == 0 will give an incorrect behavior. To correct this I think Lock in SlimDX should be changed to something like:

DataStream^ VertexBuffer::Lock( int offset, int size, LockFlags flags )
{
	int lockedSize = size == 0 ? SizeInBytes - offset : size;
		
	void* lockedPtr;
	HRESULT hr = VbPointer->Lock( offset, lockedSize, &lockedPtr, (DWORD) flags );
	GraphicsException::CheckHResult( hr );
		
	bool readOnly = (flags & LockFlags::ReadOnly) == LockFlags::ReadOnly;
	DataStream^ stream = gcnew DataStream( lockedPtr, lockedSize, true, !readOnly );
	return stream;
}

and equivalent for IndexBuffer.
Advertisement
That function is fine. The problem is that the overload you tried to use initially was written in such a way that it will never provide D3D with usable data. I'm not sure why that overload is in there; it may have been put in early on when functions were still being pulled verbatim from MDX. It's not a particularly useful function, and your fix is broken because it mutates the parameters being submitted to D3D.

In any case, after some discussion we've decided to ditch the two argument overload outright. Just use the offset, size, flags version.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Ok, I'll do that. But I belive the remaing Lock method could still cause problems if the user was to call it with offset != 0 and size == 0.
Yes, passing illegal parameters to functions frequently makes them behave in unexpected ways.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
You don't say :)
Which is a good reason to add an assert( offset == 0 || size > 0 ) or throw an ArgumentException. But I can live with it, removing the invalid Lock method worked for me.
The point is that those limitations on the parameters are D3D's business. We just exist to expose D3D to you and make your life a bit more comfy in the process. Not only that, there's no guarantee (past or future) that those restrictions on the parameters will continue to hold. And lastly, for us to double validate everything that gets passed down to D3D would be a large amount of effort for basically no gain.

My advice? Do what any reasonable D3D developer should be doing, and run with the Debug runtimes instead of retail. That way, D3D will be inclined to tell you these things and is much more likely to return an error code rather than a success code. That way, you'll get a nice loud exception out of SlimDX whining that you screwed up, and a nice little comment in the debug output about what you did wrong.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
We haven't really been in the business of validating most parameters any more strictly than the underlying API does, beyond null pointers and any constraints we impose in addition to the underlying API's (such as the "read/write" control over the DataStream object).

This is basically a maintaince and performance cost issue. D3D itself provides suitable validation for a large measure of invalid scenarios via the debug layer, and there's not a lot of motivation for us to simply layer those same checks on top. Additionally, we're more tolerant to changes in the restrictions this way, because we allow the underlying API to handle what the underlying API handles best -- it's own business.

D3D itself is not in the business of holding the programmers hand too much, and our API is designed to be a relatively thin wrapper around it (thus the name), so any additional sanity checking the user wants to do is theretheir problem.
Yeah, I get your point about being a thin wrapper. It makes sense. And I must say, apart from this little hiccup, it's a pleasure working with SlimDX.

This topic is closed to new replies.

Advertisement