Strange Text Rendering Crash In Release Mode Only

Started by
9 comments, last by BenS1 11 years, 3 months ago

I've got a strange crash that only occurs in a Release build and so its very difficult to debug the problem.

If I try single stepping through the code, the current instruction jumps around all over the place (Presumably the code is being reordered as an optimization), and when the crash does occur the debugger put the current instructions somewhere completely illogical, I don't think this is a genuine stack corruption issue, I think the debugger just isn't very good when working with release builds.

Anyway, getting to the point....

The code is a text rendering code (Originally written by MJP), which I've adapted to work with DirectXMath, and sure enough this is where the problem seems to lie.

I the text rendering class I have this structure defined:


    struct SpriteDrawData
    {
        XMMATRIX Transform;
        XMFLOAT4 Color; 
        XMFLOAT4 DrawRect;   
    };    

Then I have this member that uses this struct:


	SpriteDrawData				m_TextDrawData[constMaxBatchSize];

(Where constMaxBatchSize == 1000)

Then in the actual code I do this for each character:


	m_TextDrawData[currentDraw].Transform = XMMatrixMultiply(transform, XMMatrixTranslation(x_offset, y_offset, 0.0f));
	m_TextDrawData[currentDraw].Color = color;
	m_TextDrawData[currentDraw].DrawRect.x = desc.X;
	m_TextDrawData[currentDraw].DrawRect.y = desc.Y;
	m_TextDrawData[currentDraw].DrawRect.z = desc.Width;
	m_TextDrawData[currentDraw].DrawRect.w = desc.Height;            
	currentDraw++;

However, the first line causes a crash.

If I comment out the first line then it doesn't crash.

I've even tried changing it to this:


	m_TextDrawData[currentDraw].Transform =	XMMatrixIdentity();
	m_TextDrawData[currentDraw].Color = color;
	m_TextDrawData[currentDraw].DrawRect.x = desc.X;
	m_TextDrawData[currentDraw].DrawRect.y = desc.Y;
	m_TextDrawData[currentDraw].DrawRect.z = desc.Width;
	m_TextDrawData[currentDraw].DrawRect.w = desc.Height;            
	currentDraw++;

But it still crashes (Even when currentDraw == 0, so its not that currentDraw is overflowing).

I thought it might be an alignment problem, so I changed the structure definition to:


    __declspec(align(32)) struct SpriteDrawData
    {
        XMMATRIX Transform;
        XMFLOAT4 Color; 
        XMFLOAT4 DrawRect;   
    }; 

But that didn't help, and I don't think its necessary as XMMATRIX is already defined with 16 byte alignment.

Its really strange. It doesn't do it in a debug build, and if I comment out the code then the release build is fine too but obviously I don't see any text.

Any help would be appreciated.

Thanks

Ben

Advertisement

The only thing I can think of, your SpriteDrawData m_TextDrawData[constMaxBatchSize]; is in dynamic memory, which might not be aligned. To allocate aligned memory use _aligned_malloc() instead of new().

Thanks Zaoshi

Is it really in dynamic memory though? I don't use new (Or malloc) its simply a class member (Not a pointer).of DX11SpriteRenderer, and I have only one instance of DX11SpriteRenderer which is a member of my Renderer class. The Renderer class is a singleton so it is created with a new though.

I could try changing SpriteDrawData m_TextDrawData[constMaxBatchSize] to a pointer and using an aligned malloc (Actually, IIRC you can do an aligned new too?).

Interestingly, I've just created x64 versions (Which surprisingly didn't require any code changes at all) and now both the debug and release versions work fine. This fuels the theory that its an alignment issue as I believe x86 defaults to 8 byte alignment whereas x64 defaults to 16 byte alignment.

I'll try the aligned malloc.

Thanks

Ben

This is an alignment issue. You can also try pragma directive to pack everything to 1 byte boundary and then build your code.

Also, to check if this really is the issue, simply look for the value of sizeof(SpriteDrawData).

Ok, it is clearly an alignment issue. I added this:


	g_Logger.DebugOut("Address of Matrix = %p, alignment = %d", &m_TextDrawData[currentDraw].Transform, __alignof(SpriteDrawData));

	m_TextDrawData[currentDraw].Transform = XMMatrixMultiply(transform, XMMatrixTranslation(x_offset, y_offset, 0.0f));

And it output "Address of Matrix = 008A06F8, alignment = 16"

As you can see, it says the alignment is 16 but the actual address of the matrix is not on a 16 byte boundary.

The question is why. XMMATRIX is defined as "__declspec(align(16)) struct XMMATRIX", which the documentation suggests will cause it to be allocated on 16 byte boundary's. The docs also say it will be inherited, so as struct SpriteDrawData includes an XMMATRIX member the SpriteDrawData itself will be aligned accordingly to honor its alignment requirements. But clearly that's not happening here.

From doing some reading around today it appears that pragma pack actually only manages the packing within a structure but doesn't actually ensure that the structure is aligned to a multiple of the pack value.

Thanks

Ben

x64 indeed has 16 byte alignment for new() calls.

Since you create Renderer using new() it might not be aligned, so you could try using _aligned_malloc() and it should fix it.

Thanks again Zaoshi,

If I use _aligned_malloc then it'll allocate the memory, but it won't called the constructors and all the other stuff that new does. Similarly with free vs delete.

I've actually given up on the alignment and worked around the problem by changing from using XMMATRIX (Which requires alignment) to using XMFLOAT4X4 (Which doesn't require alignment) and using the Load/Store functions to convert between them if and where necessary. There doesn't seem to be any performance impact at all.

Thanks everyone for your help.

Ben


Renderer *renderer = (Renderer*)_aligned_malloc(sizeof(Renderer), 16); // allocate
new (&renderer) Renderer(); // manually call constructor

Thanks again Zaoshi

Can I then free the memory using delete, or does it have to be freed using free?

If it can be freed using delete then it'll allow me to use a smart pointer (I don't like using naked raw pointers if I can help it).

Thanks

Ben

Since you used _aligned_malloc you'll have to use _aligned_free.


renderer->~Renderer(); // manually call destructor_aligned_free(renderer);


Edit:
You could overload new and delete operators as well, then you won't have to call constructor/destructor manually, and it allows use of smart pointers.:


struct AlignedAllocator {
	void* operator new(size_t size) {
		return _aligned_malloc(size, 16);
	}

	void operator delete(void *ptr) {
		_aligned_free(ptr);
	}
};

struct Renderer : AlignedAllocator {
	// ...
};

This topic is closed to new replies.

Advertisement