#include "pch.h" #include "DXSpriteBatch.h" #include "SpriteBatch_VS.h" #include "SpriteBatch_PS.h" #include #include #include "OnResizeBuffer.h" #include "OnFrameBuffer.h" using namespace std; namespace Engine { #define VERTICES_PER_SPRITE 4 #define INDICES_PER_SPRITE 6 #define MAX_BATCH_SIZE 2048 #define VERTEX_SIZE sizeof(SpriteVertex) #define INDEX_SIZE sizeof(unsigned short) #define VERTEX_BUFFER_SIZE MAX_BATCH_SIZE * VERTICES_PER_SPRITE * VERTEX_SIZE #define INDEX_BUFFER_SIZE MAX_BATCH_SIZE * INDICES_PER_SPRITE * INDEX_SIZE #define NEAR_PLANE 0.001f #define FAR_PLANE 1000.0f #define DEFAULT_DEPTH 1.0f DXSpriteBatch::DXSpriteBatch(ID3D11Device1 *device, ID3D11DeviceContext1 *context, float width, float height) : device(device), context(context), batchedSprites(0), beginCalled(false), customPS(nullptr), _useFilter(false), customSRV(nullptr) { this->queuedSprites.reserve(MAX_BATCH_SIZE); this->LoadShaders(); this->InitializeBuffers(); this->CreateStates(); this->UpdateProjectionMatrix(width, height); } DXSpriteBatch::~DXSpriteBatch(void) { this->device = nullptr; this->context = nullptr; } void DXSpriteBatch::LoadShaders(void) { if(FAILED(this->device->CreateVertexShader( SPRITEBATCH_VS, sizeof(SPRITEBATCH_VS), NULL, &this->vs))) { } HRESULT test; if(FAILED(test =this->device->CreatePixelShader( SPRITEBATCH_PS, sizeof(SPRITEBATCH_PS), NULL, &this->ps))) { } D3D11_INPUT_ELEMENT_DESC inputElements[] = { {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, {"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0} }; if(FAILED(this->device->CreateInputLayout( inputElements, ARRAYSIZE(inputElements), SPRITEBATCH_VS, sizeof(SPRITEBATCH_VS), &this->inputLayout))) { } } void DXSpriteBatch::InitializeBuffers(void) { CD3D11_BUFFER_DESC vertexDesc( VERTEX_BUFFER_SIZE, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); ComPtr tmpVertexBuffer; if(FAILED(this->device->CreateBuffer(&vertexDesc, nullptr, tmpVertexBuffer.GetAddressOf()))) { } vector indices; indices.reserve(INDICES_PER_SPRITE * MAX_BATCH_SIZE); static_assert(INDICES_PER_SPRITE == 6 && VERTICES_PER_SPRITE == 4, "Changing the number of indices per sprite also \ requires changing the initial data generation for \ the index buffer."); for(int i = 0; i < MAX_BATCH_SIZE * VERTICES_PER_SPRITE; i += VERTICES_PER_SPRITE) { indices.push_back(i); indices.push_back(i + 1); indices.push_back(i + 2); indices.push_back(i + 1); indices.push_back(i + 3); indices.push_back(i + 2); } D3D11_SUBRESOURCE_DATA data = {0}; data.pSysMem = &indices.front(); CD3D11_BUFFER_DESC indexDesc( INDEX_BUFFER_SIZE, D3D11_BIND_INDEX_BUFFER, D3D11_USAGE_IMMUTABLE); ComPtr tmpIndexBuffer; if(FAILED(this->device->CreateBuffer(&indexDesc, &data, tmpIndexBuffer.GetAddressOf()))) { } CD3D11_BUFFER_DESC onResizeDesc( sizeof(OnResizeBuffer), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); ComPtr tmpOnResizeBuffer; if(FAILED(this->device->CreateBuffer(&onResizeDesc, nullptr, tmpOnResizeBuffer.GetAddressOf()))) { } CD3D11_BUFFER_DESC onFrameDesc( sizeof(OnFrameBuffer), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); ComPtr tmpOnFrameBuffer; if(FAILED(this->device->CreateBuffer(&onFrameDesc, nullptr, tmpOnFrameBuffer.GetAddressOf()))) { } this->onFrameBuffer = tmpOnFrameBuffer; this->vertexBuffer = tmpVertexBuffer; this->indexBuffer = tmpIndexBuffer; this->onResizeBuffer = tmpOnResizeBuffer; } void DXSpriteBatch::CreateStates() { // Blend state D3D11_BLEND_DESC blendDesc; ZeroMemory(&blendDesc, sizeof(D3D11_BLEND_DESC)); blendDesc.RenderTarget[0].BlendEnable = true; blendDesc.RenderTarget[0].SrcBlend = blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_SRC_ALPHA; blendDesc.RenderTarget[0].DestBlend = blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; blendDesc.RenderTarget[0].BlendOp = blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; ComPtr tmpBlend; if(FAILED(this->device->CreateBlendState(&blendDesc, tmpBlend.GetAddressOf()))) { } // Additive Blend State blendDesc.RenderTarget[0].BlendEnable = true; blendDesc.RenderTarget[0].SrcBlend = blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; blendDesc.RenderTarget[0].DestBlend = blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; blendDesc.RenderTarget[0].BlendOp = blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; ComPtr tmpAdditiveBlend; if(FAILED(this->device->CreateBlendState(&blendDesc, tmpAdditiveBlend.GetAddressOf()))) { } // Depth stencil state D3D11_DEPTH_STENCIL_DESC depthDesc; ZeroMemory(&depthDesc, sizeof(D3D11_DEPTH_STENCIL_DESC)); depthDesc.DepthEnable = true; depthDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; depthDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL; ComPtr tmpDepth; if(FAILED(this->device->CreateDepthStencilState(&depthDesc, tmpDepth.GetAddressOf()))) { } // Rasterizer state D3D11_RASTERIZER_DESC rasterizerDesc; ZeroMemory(&rasterizerDesc, sizeof(D3D11_RASTERIZER_DESC)); rasterizerDesc.CullMode = D3D11_CULL_NONE; rasterizerDesc.FillMode = D3D11_FILL_SOLID; rasterizerDesc.DepthClipEnable = true; rasterizerDesc.MultisampleEnable = true; ComPtr tmpRasterizer; if(FAILED(this->device->CreateRasterizerState(&rasterizerDesc, tmpRasterizer.GetAddressOf()))) { } D3D11_SAMPLER_DESC samplerDesc; ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC)); samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; //D3D11_FILTER_ANISOTROPIC; //D3D11_FILTER_MIN_MAG_MIP_POINT; //D3D11_FILTER_MIN_MAG_MIP_LINEAR samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.MaxLOD = FLT_MAX; samplerDesc.MaxAnisotropy = 1; samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER; ComPtr tmpSampler; if(FAILED(this->device->CreateSamplerState(&samplerDesc, tmpSampler.GetAddressOf()))) { } this->alphaBlendState = tmpBlend; this->depthStencilState = tmpDepth; this->rasterizerState = tmpRasterizer; this->samplerState = tmpSampler; this->additiveBlendState = tmpAdditiveBlend; } void DXSpriteBatch::OnResize(float width, float height) { this->UpdateProjectionMatrix(width, height); } void DXSpriteBatch::UpdateProjectionMatrix(float width, float height) { OnResizeBuffer onResize; //this->dxdevice->GetBackbufferSize(&width, &height); onResize.projection = XMMatrixOrthographicOffCenterLH(0, width, height, 0, NEAR_PLANE, FAR_PLANE); D3D11_MAPPED_SUBRESOURCE mappedSubresource; if(FAILED(this->context->Map(this->onResizeBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubresource))) { } *(OnResizeBuffer *)mappedSubresource.pData = onResize; this->context->Unmap(this->onResizeBuffer.Get(), 0); } void DXSpriteBatch::SetCustomPixelShader(void *customPS) { this->customPS = (ID3D11PixelShader *) customPS; } void DXSpriteBatch::SetCustomShaderResourceView(void *customSRV) { this->customSRV = (ID3D11ShaderResourceView *)customSRV; } void DXSpriteBatch::Begin(XMMATRIX &world, bool useFilter) { if (_useFilter != useFilter) { //update filter value _useFilter = useFilter; //create sampler state D3D11_SAMPLER_DESC samplerDesc; ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC)); if (_useFilter) samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; else samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; //D3D11_FILTER_ANISOTROPIC; //D3D11_FILTER_MIN_MAG_MIP_POINT; //D3D11_FILTER_MIN_MAG_MIP_LINEAR samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.MaxLOD = FLT_MAX; samplerDesc.MaxAnisotropy = 1; samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER; ComPtr tmpSampler; if (FAILED(this->device->CreateSamplerState(&samplerDesc, tmpSampler.GetAddressOf()))) { } this->samplerState = tmpSampler; } if(beginCalled) { return; } beginCalled = true; this->queuedSprites.clear(); this->batchedSprites = 0; this->heapSort = HeapCompareBackToFront; OnFrameBuffer onFrame; onFrame.world = world; D3D11_MAPPED_SUBRESOURCE mappedSubresource; if(FAILED(this->context->Map(this->onFrameBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubresource))) { } *(OnFrameBuffer *)mappedSubresource.pData = onFrame; this->context->Unmap(this->onFrameBuffer.Get(), 0); } void DXSpriteBatch::Draw(const Rectangle &targetArea, const Rectangle *sourceArea, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, float depth, float rotation, Color &color) { if(!beginCalled) { return; } SpriteInfo info; info.TargetArea = XMFLOAT4( (float) targetArea.X, (float) targetArea.Y, (float) targetArea.Width, (float) targetArea.Height ); if(sourceArea) { info.SourceArea = XMFLOAT4( (float) sourceArea->X, (float) sourceArea->Y, (float) sourceArea->Width, (float) sourceArea->Height ); } else { D3D11_TEXTURE2D_DESC desc; texture->GetDesc(&desc); info.SourceArea = XMFLOAT4A(0.0f, 0.0f, desc.Width, desc.Height); } info.Rotation = rotation; info.Depth = depth; info.Texture = texture; info.TextureSRV = textureSRV; info.Color = color; this->QueueSprite(info); //this->FlushBatch(); } void DXSpriteBatch::Draw(const Rectangle &target, const Rectangle *source, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, float depth, Color &color) { this->Draw(target, source, textureSRV, texture, depth, 0.0f, color); } void DXSpriteBatch::Draw(const Rectangle &target, const Rectangle *source, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, Color &color) { this->Draw(target, source, textureSRV, texture, DEFAULT_DEPTH, 0.0f, color); } void DXSpriteBatch::Draw(const Rectangle &target, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, float depth, float rotation, Color &color) { Rectangle textureBounds; D3D11_TEXTURE2D_DESC desc; texture->GetDesc(&desc); textureBounds.X = 0; textureBounds.Y = 0; textureBounds.Width = desc.Width; textureBounds.Height = desc.Height; this->Draw(target, &textureBounds, textureSRV, texture, depth, rotation, color); } void DXSpriteBatch::Draw(const Rectangle &target, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, float depth, Color &color) { this->Draw(target, textureSRV, texture, depth, 0.0f, color); } void DXSpriteBatch::Draw(const Rectangle &target, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, Color &color) { this->Draw(target, textureSRV, texture, DEFAULT_DEPTH, 0.0f, color); } void DXSpriteBatch::Draw(const Vector2 &target, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, float depth, float rotation, Color &color) { Rectangle textureBounds; Rectangle targetArea; D3D11_TEXTURE2D_DESC desc; texture->GetDesc(&desc); textureBounds.X = 0; textureBounds.Y = 0; targetArea.X = (int) target.X; targetArea.Y = (int) target.Y; targetArea.Width = textureBounds.Width = desc.Width; targetArea.Height = textureBounds.Height = desc.Height; this->Draw(targetArea, &textureBounds, textureSRV, texture, depth, rotation, color); } void DXSpriteBatch::Draw(const Vector2 &target, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, float depth, Color &color) { this->Draw(target, textureSRV, texture, depth, 0.0f, color); } void DXSpriteBatch::Draw(const Vector2 &target, ID3D11ShaderResourceView *textureSRV, ID3D11Texture2D *texture, Color &color) { this->Draw(target, textureSRV, texture, DEFAULT_DEPTH, 0.0f, color); } void DXSpriteBatch::QueueSprite(SpriteInfo &info) { this->queuedSprites.push_back(info); this->batchedSprites++; } void DXSpriteBatch::End(void) { if(!beginCalled) { } this->beginCalled = false; if(this->batchedSprites == 0) { this->customPS = nullptr; this->customSRV = nullptr; return; } //sort_heap(this->queuedSprites.begin(), this->queuedSprites.end(), this->heapSort); this->FlushBatch(); this->customPS = nullptr; this->customSRV = nullptr; } void DXSpriteBatch::FlushBatch(void) { // Put all sprites into the vertex buffer first to avoid frequent mapping and unmapping for each batch. D3D11_MAPPED_SUBRESOURCE mappedBuffer; if(FAILED(this->context->Map(this->vertexBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedBuffer))) { } // Fill vertex buffer SpriteVertex *vertices = (SpriteVertex *) mappedBuffer.pData; UINT i = 0; for(; i < this->batchedSprites; i++) { SpriteInfo info = this->queuedSprites.at(i); XMVECTOR textureSize; XMVECTOR invTextureSize; D3D11_TEXTURE2D_DESC desc; info.Texture->GetDesc(&desc); #if !defined(_M_ARM) textureSize.m128_f32[0] = desc.Width; textureSize.m128_f32[1] = desc.Height; textureSize = XMVectorSwizzle(textureSize, 0, 1, 0, 1); invTextureSize.m128_f32[0] = 1.0f / textureSize.m128_f32[0]; invTextureSize.m128_f32[1] = 1.0f / textureSize.m128_f32[1]; #else textureSize.n128_f32[0] = desc.Width; textureSize.n128_f32[1] = desc.Height; textureSize = XMVectorSwizzle<0,1,0,1>(textureSize); invTextureSize.n128_f32[0] = 1.0f / textureSize.n128_f32[0]; invTextureSize.n128_f32[1] = 1.0f / textureSize.n128_f32[1]; #endif invTextureSize = XMVectorSwizzle(invTextureSize,0,1,0,1); // Load sprite parameters into SIMD registers. XMVECTOR source = XMLoadFloat4(&info.SourceArea); XMVECTOR destination = XMLoadFloat4(&info.TargetArea); float rotation = info.Rotation; float depth = info.Depth; // Extract the source and destination sizes into separate vectors. XMVECTOR sourceSize = XMVectorSwizzle(source, 2, 3, 2, 3); XMVECTOR destinationSize = XMVectorSwizzle(destination, 2, 3, 2, 3); XMVECTOR halfDestinationSize = destinationSize / 2.0f; source *= invTextureSize; sourceSize *= invTextureSize; XMVECTOR rotation1; XMVECTOR rotation2; if(rotation != 0.0f) { float sin, cos; XMScalarSinCos(&sin, &cos, rotation); XMVECTOR sinV = XMLoadFloat(&sin); XMVECTOR cosV = XMLoadFloat(&cos); rotation1 = XMVectorMergeXY(cosV, sinV); rotation2 = XMVectorMergeXY(-sinV, cosV); }else { #if !defined(_M_ARM) rotation1.m128_f32[0] = 1.0f; rotation1.m128_f32[1] = 0.0f; rotation1.m128_f32[2] = 0.0f; rotation1.m128_f32[3] = 0.0f; rotation2.m128_f32[0] = 0.0f; rotation2.m128_f32[1] = 1.0f; rotation2.m128_f32[2] = 0.0f; rotation2.m128_f32[3] = 0.0f; #else rotation1.n128_f32[0] = 1.0f; rotation1.n128_f32[1] = 0.0f; rotation1.n128_f32[2] = 0.0f; rotation1.n128_f32[3] = 0.0f; rotation2.n128_f32[0] = 0.0f; rotation2.n128_f32[1] = 1.0f; rotation2.n128_f32[2] = 0.0f; rotation2.n128_f32[3] = 0.0f; #endif } static XMVECTORF32 corners[VERTICES_PER_SPRITE] = { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; // Calculate sprite vertices for(int j = 0; j < VERTICES_PER_SPRITE; j++) { SpriteVertex vertex; // Rotate the vertex around the sprites center XMVECTOR corner = corners[j] * destinationSize; corner = XMVectorSubtract(corner, halfDestinationSize); // Shift center into origin XMVECTOR pos1 = XMVectorMultiplyAdd(XMVectorSplatX(corner), rotation1, destination); XMVECTOR pos2 = XMVectorMultiplyAdd(XMVectorSplatY(corner), rotation2, XMVectorAdd(pos1, halfDestinationSize)); XMVECTOR position = pos2; #if !defined(_M_ARM) position.m128_f32[2] = NEAR_PLANE; position.m128_f32[3] = 1.0f; vertex.Position.x = position.m128_f32[0]; vertex.Position.y = position.m128_f32[1]; vertex.Position.z = position.m128_f32[2]; #else position.n128_f32[2] = NEAR_PLANE; position.n128_f32[3] = 1.0f; vertex.Position.x = position.n128_f32[0]; vertex.Position.y = position.n128_f32[1]; vertex.Position.z = position.n128_f32[2]; #endif //XMStoreFloat4(reinterpret_cast(&vertex.Position), position); XMVECTOR texCoord = XMVectorMultiplyAdd(corners[j], sourceSize, source); XMStoreFloat2(&vertex.TexCoord, texCoord); vertex.Color = info.Color; *vertices = vertex; vertices++; } } this->context->Unmap(this->vertexBuffer.Get(), 0); // Render all sprites SpriteInfo lastSprite = this->queuedSprites.at(0); i = 0; UINT batchBegin = i; for(; i < this->batchedSprites; i++) { SpriteInfo info = this->queuedSprites.at(i); if((info.Texture.Get() != lastSprite.Texture.Get())) { this->RenderBatch(batchBegin, i - 1, lastSprite); lastSprite = info; batchBegin = i; } } // Render last batch of sprites RenderBatch(batchBegin, i - 1, lastSprite); this->queuedSprites.clear(); this->batchedSprites = 0; } void DXSpriteBatch::RenderBatch(UINT start, UINT end, SpriteInfo &spriteInfo) { ID3D11ShaderResourceView *srv = nullptr; srv = spriteInfo.TextureSRV.Get(); this->context->OMSetBlendState(this->alphaBlendState.Get(), nullptr, 0xFFFFFFFF); this->context->OMSetDepthStencilState(this->depthStencilState.Get(), 0); this->context->RSSetState(this->rasterizerState.Get()); this->context->PSSetSamplers(0, 1, this->samplerState.GetAddressOf()); // Shaders this->context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); this->context->IASetInputLayout(this->inputLayout.Get()); this->context->VSSetShader(this->vs.Get(), nullptr, 0); if(this->customPS) { this->context->PSSetShader(this->customPS.Get(), nullptr, 0); }else { this->context->PSSetShader(this->ps.Get(), nullptr, 0); } this->context->PSSetShaderResources(0, 1, &srv); if (this->customSRV) //custom shader resource { ID3D11ShaderResourceView *customsrv = nullptr; customsrv = this->customSRV.Get(); this->context->PSSetShaderResources(1, 1, &customsrv); //IMPORTANT: the following line does *NOT* work, even though it compile //this->context->PSSetShaderResources(1, 1, &this->customSRV); } // Buffers UINT stride = sizeof(SpriteVertex); UINT offset = 0; this->context->IASetVertexBuffers(0, 1, this->vertexBuffer.GetAddressOf(), &stride, &offset); this->context->IASetIndexBuffer(this->indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0); this->context->VSSetConstantBuffers(0, 1, this->onResizeBuffer.GetAddressOf()); this->context->VSSetConstantBuffers(1, 1, this->onFrameBuffer.GetAddressOf()); // Draw this->context->DrawIndexed( INDICES_PER_SPRITE * (end - start + 1), start * INDICES_PER_SPRITE, 0 ); } bool HeapCompareByTexture(const SpriteInfo &info1, const SpriteInfo &info2) { return true;// ((info1.ContentType == info2.ContentType) && (info1.TextureID < info2.TextureID)) || //(info1.ContentType < info2.ContentType); } bool HeapCompareBackToFront(const SpriteInfo &info1, const SpriteInfo &info2) { return (info1.Depth > info2.Depth) || ((info1.Depth == info2.Depth) && HeapCompareByTexture(info1, info2)) ; } bool HeapCompareFrontToBack(const SpriteInfo &info1, const SpriteInfo &info2) { return (info1.Depth < info2.Depth) || ((info1.Depth == info2.Depth) && HeapCompareByTexture(info1, info2)) ; } }