1 //// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
2 //// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
3 //// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
4 //// PARTICULAR PURPOSE.
6 //// Copyright (c) Microsoft Corporation. All rights reserved
9 #include "BasicSprites.h"
10 #include "DirectXSample.h"
11 #include "BasicLoader.h"
13 // Display a performance warning message in Debug builds.
15 #pragma message("Warning: SpriteBatch performance is significantly decreased in the Debug configuration. Switch to the Release configuration for better performance.")
18 using namespace Microsoft::WRL;
19 using namespace BasicSprites;
21 SpriteBatch::SpriteBatch() :
26 void SpriteBatch::Initialize(
27 _In_ ID3D11Device1* d3dDevice,
28 _In_ int capacity = 1024
31 m_d3dDevice = d3dDevice;
32 m_d3dDevice->GetImmediateContext1(&m_d3dContext);
33 m_capacity = capacity;
35 // Determine the technique that will be used to render the sprites.
36 auto featureLevel = m_d3dDevice->GetFeatureLevel();
38 if (featureLevel >= D3D_FEATURE_LEVEL_10_0)
40 // On DirectX 10+ devices, the Geometry Shader allows the sprite vertices to be
41 // generated on the GPU, significantly reducing memory bandwidth requirements.
42 m_technique = RenderTechnique::GeometryShader;
44 else if (featureLevel >= D3D_FEATURE_LEVEL_9_3)
46 // On DirectX 9.3+ devices, instancing allows shared sprite geometry with unique
47 // per-sprite instance parameters, eliminating redundant data transfer.
48 m_technique = RenderTechnique::Instancing;
52 // On devices that do not support Instancing, sprite vertex data must be replicated
53 // in order to achieve the desired effect.
54 m_technique = RenderTechnique::Replication;
56 if (capacity > static_cast<int>(Parameters::MaximumCapacityCompatible))
58 // The index buffer format for feature-level 9.1 devices may only be 16 bits.
59 // With 4 vertices per sprite, this allows a maximum of (1 << 16) / 4 sprites.
60 throw ref new Platform::InvalidArgumentException();
64 // Create the texture sampler.
66 D3D11_SAMPLER_DESC samplerDesc;
67 ZeroMemory(&samplerDesc, sizeof(samplerDesc));
68 samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
69 samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
70 samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
71 samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
72 samplerDesc.MipLODBias = 0.0f;
73 samplerDesc.MaxAnisotropy = 0;
74 samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
75 samplerDesc.BorderColor[0] = 0.0f;
76 samplerDesc.BorderColor[1] = 0.0f;
77 samplerDesc.BorderColor[2] = 0.0f;
78 samplerDesc.BorderColor[3] = 0.0f;
79 samplerDesc.MinLOD = 0.0f;
80 samplerDesc.MaxLOD = FLT_MAX;
83 m_d3dDevice->CreateSamplerState(
89 // Create the blend states.
91 D3D11_BLEND_DESC1 blendDesc;
92 ZeroMemory(&blendDesc, sizeof(blendDesc));
93 blendDesc.AlphaToCoverageEnable = false;
94 blendDesc.IndependentBlendEnable = false;
95 blendDesc.RenderTarget[0].BlendEnable = true;
96 blendDesc.RenderTarget[0].LogicOpEnable = false;
97 blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
98 blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
99 blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
100 blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
101 blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
102 blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
103 blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
106 m_d3dDevice->CreateBlendState1(
112 blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE;
115 m_d3dDevice->CreateBlendState1(
117 &m_blendStateAdditive
121 BasicLoader^ loader = ref new BasicLoader(m_d3dDevice.Get());
123 if (m_technique == RenderTechnique::GeometryShader)
125 D3D11_INPUT_ELEMENT_DESC layoutDesc[] =
127 { "TRANSFORM", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
128 { "TRANSFORM", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
129 { "TRANSFORM", 2, DXGI_FORMAT_R32_FLOAT, 0, 16, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
130 { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 20, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
133 "BasicSprites.GeometryShader.vs.cso",
135 ARRAYSIZE(layoutDesc),
140 "BasicSprites.GeometryShader.gs.cso",
144 else if (m_technique == RenderTechnique::Instancing)
146 D3D11_INPUT_ELEMENT_DESC layoutDesc[] =
148 { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
149 { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 },
150 { "TRANSFORM", 0, DXGI_FORMAT_R32G32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
151 { "TRANSFORM", 1, DXGI_FORMAT_R32G32_FLOAT, 1, 8, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
152 { "TRANSFORM", 2, DXGI_FORMAT_R32_FLOAT, 1, 16, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
153 { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 1, 20, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
156 "BasicSprites.Instancing.vs.cso",
158 ARRAYSIZE(layoutDesc),
163 else if (m_technique == RenderTechnique::Replication)
165 D3D11_INPUT_ELEMENT_DESC layoutDesc[] =
167 { "POSITIONT", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
168 { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 },
169 { "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0 }
172 "BasicSprites.Replication.vs.cso",
174 ARRAYSIZE(layoutDesc),
181 "BasicSprites.ps.cso",
187 if (m_technique == RenderTechnique::GeometryShader)
189 // Create the instance data buffer.
192 m_d3dDevice->CreateBuffer(
194 m_capacity * sizeof(InstanceData),
195 D3D11_BIND_VERTEX_BUFFER,
200 &m_instanceDataBuffer
204 m_instanceData.reset(new InstanceData[m_capacity]);
206 else if (m_technique == RenderTechnique::Instancing)
208 // Create the vertex buffer.
210 InstancingVertex vertexBufferData[] =
212 { float2(-1.0f, 1.0f), float2(0.0f, 0.0f) },
213 { float2( 1.0f, 1.0f), float2(1.0f, 0.0f) },
214 { float2(-1.0f, -1.0f), float2(0.0f, 1.0f) },
215 { float2( 1.0f, -1.0f), float2(1.0f, 1.0f) }
218 D3D11_SUBRESOURCE_DATA vertexInitialData;
219 vertexInitialData.pSysMem = vertexBufferData;
220 vertexInitialData.SysMemPitch = 0;
221 vertexInitialData.SysMemSlicePitch = 0;
224 m_d3dDevice->CreateBuffer(
226 sizeof(vertexBufferData),
227 D3D11_BIND_VERTEX_BUFFER,
236 // Create the instance data buffer.
239 m_d3dDevice->CreateBuffer(
241 m_capacity * sizeof(InstanceData),
242 D3D11_BIND_VERTEX_BUFFER,
247 &m_instanceDataBuffer
251 m_instanceData.reset(new InstanceData[m_capacity]);
253 // Create the index buffer.
255 unsigned int indexBufferData[] =
261 D3D11_SUBRESOURCE_DATA indexInitialData;
262 indexInitialData.pSysMem = indexBufferData;
263 indexInitialData.SysMemPitch = 0;
264 indexInitialData.SysMemSlicePitch = 0;
267 m_d3dDevice->CreateBuffer(
269 sizeof(indexBufferData),
270 D3D11_BIND_INDEX_BUFFER,
279 else if (m_technique == RenderTechnique::Replication)
281 // Create the vertex buffer.
284 m_d3dDevice->CreateBuffer(
286 m_capacity * 4 * sizeof(ReplicationVertex),
287 D3D11_BIND_VERTEX_BUFFER,
296 m_vertexData.reset(new ReplicationVertex[m_capacity * 4]);
298 // Create the index buffer.
300 std::unique_ptr<unsigned short[]> indexBufferData(new unsigned short[m_capacity * 6]);
302 for (int i = 0; i < m_capacity; i++)
304 indexBufferData[i * 6 + 0] = i * 4 + 0;
305 indexBufferData[i * 6 + 1] = i * 4 + 1;
306 indexBufferData[i * 6 + 2] = i * 4 + 2;
307 indexBufferData[i * 6 + 3] = i * 4 + 1;
308 indexBufferData[i * 6 + 4] = i * 4 + 3;
309 indexBufferData[i * 6 + 5] = i * 4 + 2;
312 D3D11_SUBRESOURCE_DATA initialData;
313 initialData.pSysMem = indexBufferData.get();
314 initialData.SysMemPitch = 0;
315 initialData.SysMemSlicePitch = 0;
318 m_d3dDevice->CreateBuffer(
320 m_capacity * 6 * sizeof(unsigned short),
321 D3D11_BIND_INDEX_BUFFER,
331 if (m_technique == RenderTechnique::GeometryShader || m_technique == RenderTechnique::Instancing)
333 // Both the Geometry Shader and Instancing techniques scale geometry in shader code.
334 // As a result, they require information about the render target size.
336 m_d3dDevice->CreateBuffer(
338 16, // Constant buffer sizes must be a multiple of 16 bytes. 16 is sufficient for the required float2 data.
339 D3D11_BIND_CONSTANT_BUFFER,
341 D3D11_CPU_ACCESS_WRITE
344 &m_renderTargetInfoCbuffer
350 void SpriteBatch::AddTexture(
351 _In_ ID3D11Texture2D* texture
354 TextureMapElement mapElement;
356 m_d3dDevice->CreateShaderResourceView(
358 &CD3D11_SHADER_RESOURCE_VIEW_DESC(
360 D3D11_SRV_DIMENSION_TEXTURE2D
366 D3D11_TEXTURE2D_DESC textureDesc;
367 texture->GetDesc(&textureDesc);
368 mapElement.size = float2(
369 static_cast<float>(textureDesc.Width),
370 static_cast<float>(textureDesc.Height)
373 m_textureMap[texture] = mapElement;
376 void SpriteBatch::RemoveTexture(
377 _In_ ID3D11Texture2D* texture
380 m_textureMap.erase(texture);
383 void SpriteBatch::Begin()
385 // Reset internal sprite data.
387 m_numSpritesDrawn = 0;
389 m_spriteRuns.clear();
391 // Get the current render target dimensions and logical DPI.
393 ComPtr<ID3D11RenderTargetView> renderTargetView;
394 m_d3dContext->OMGetRenderTargets(
400 ComPtr<ID3D11Resource> renderTarget;
401 renderTargetView->GetResource(&renderTarget);
403 ComPtr<ID3D11Texture2D> renderTargetTexture;
404 renderTarget.As(&renderTargetTexture);
406 D3D11_TEXTURE2D_DESC renderTargetTextureDesc;
407 renderTargetTexture->GetDesc(&renderTargetTextureDesc);
409 m_renderTargetSize = float2(
410 static_cast<float>(renderTargetTextureDesc.Width),
411 static_cast<float>(renderTargetTextureDesc.Height)
414 m_dpi = Windows::Graphics::Display::DisplayProperties::LogicalDpi;
417 void SpriteBatch::End()
419 // If no sprites were drawn, do nothing.
420 if (m_numSpritesDrawn == 0)
425 // Save the final sprite run info.
427 SpriteRunInfo runInfo;
428 runInfo.textureView = m_currentTextureView;
429 runInfo.blendState = m_currentBlendState;
430 runInfo.numSprites = m_spritesInRun;
431 m_spriteRuns.push_back(runInfo);
433 // Update the buffer data.
435 if (m_technique == RenderTechnique::GeometryShader || m_technique == RenderTechnique::Instancing)
437 m_d3dContext->UpdateSubresource(
438 m_instanceDataBuffer.Get(),
440 &CD3D11_BOX(0, 0, 0, sizeof(InstanceData) * m_numSpritesDrawn, 1, 1),
441 m_instanceData.get(),
446 else if (m_technique == RenderTechnique::Replication)
448 m_d3dContext->UpdateSubresource(
449 m_vertexBuffer.Get(),
451 &CD3D11_BOX(0, 0, 0, sizeof(ReplicationVertex) * m_numSpritesDrawn * 4, 1, 1),
458 if (m_technique == RenderTechnique::GeometryShader || m_technique == RenderTechnique::Instancing)
460 D3D11_MAPPED_SUBRESOURCE mappedSubresource;
462 m_renderTargetInfoCbuffer.Get(),
464 D3D11_MAP_WRITE_DISCARD,
468 *static_cast<float2*>(mappedSubresource.pData) = m_renderTargetSize;
470 m_renderTargetInfoCbuffer.Get(),
475 // Set the pipeline state
477 if (m_technique == RenderTechnique::Instancing)
479 m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
480 m_d3dContext->IASetIndexBuffer(
482 DXGI_FORMAT_R32_UINT,
486 else if (m_technique == RenderTechnique::Replication)
488 m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
489 m_d3dContext->IASetIndexBuffer(
491 DXGI_FORMAT_R16_UINT,
495 else if (m_technique == RenderTechnique::GeometryShader)
497 m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
498 m_d3dContext->GSSetShader(
499 m_geometryShader.Get(),
505 m_d3dContext->IASetInputLayout(m_inputLayout.Get());
507 m_d3dContext->VSSetShader(
508 m_vertexShader.Get(),
513 if (m_technique == RenderTechnique::GeometryShader)
515 m_d3dContext->GSSetConstantBuffers(
518 m_renderTargetInfoCbuffer.GetAddressOf()
521 else if (m_technique == RenderTechnique::Instancing)
523 m_d3dContext->VSSetConstantBuffers(
526 m_renderTargetInfoCbuffer.GetAddressOf()
530 m_d3dContext->PSSetShader(
536 m_d3dContext->PSSetSamplers(
539 m_linearSampler.GetAddressOf()
542 if (m_technique == RenderTechnique::GeometryShader)
544 unsigned int stride = sizeof(InstanceData);
545 unsigned int offset = 0;
546 m_d3dContext->IASetVertexBuffers(
549 m_instanceDataBuffer.GetAddressOf(),
554 else if (m_technique == RenderTechnique::Instancing)
556 unsigned int stride = sizeof(InstancingVertex);
557 unsigned int offset = 0;
558 m_d3dContext->IASetVertexBuffers(
561 m_vertexBuffer.GetAddressOf(),
566 else if (m_technique == RenderTechnique::Replication)
568 unsigned int stride = sizeof(ReplicationVertex);
569 unsigned int offset = 0;
570 m_d3dContext->IASetVertexBuffers(
573 m_vertexBuffer.GetAddressOf(),
579 // Draw each sprite run
581 unsigned int indexBase = 0;
582 for (auto runIterator = m_spriteRuns.begin(); runIterator != m_spriteRuns.end(); runIterator++)
584 m_d3dContext->PSSetShaderResources(
587 &runIterator->textureView
590 const FLOAT blendFactor[] = {0.0f, 0.0f, 0.0f, 0.0f};
592 m_d3dContext->OMSetBlendState(
593 runIterator->blendState,
598 if (m_technique == RenderTechnique::GeometryShader)
600 unsigned int instancesToDraw = runIterator->numSprites;
601 m_d3dContext->DrawInstanced(
607 indexBase += instancesToDraw;
609 else if (m_technique == RenderTechnique::Instancing)
611 unsigned int instancesToDraw = runIterator->numSprites;
612 unsigned int stride = sizeof(InstanceData);
613 unsigned int offset = indexBase * stride;
614 // Instance data offset must be zero for the draw call on feature level 9.3 and below.
615 // Instead, set the offset in the input assembler.
616 m_d3dContext->IASetVertexBuffers(
619 m_instanceDataBuffer.GetAddressOf(),
623 m_d3dContext->DrawIndexedInstanced(
630 indexBase += instancesToDraw;
632 else if (m_technique == RenderTechnique::Replication)
634 unsigned int indicesToDraw = runIterator->numSprites * 6;
635 m_d3dContext->DrawIndexed(indicesToDraw, indexBase, 0);
636 indexBase += indicesToDraw;
641 unsigned int SpriteBatch::MakeUnorm(float4 color)
643 unsigned int r = max(0, min(255, static_cast<unsigned int>(color.r * 255.0f)));
644 unsigned int g = max(0, min(255, static_cast<unsigned int>(color.g * 255.0f)));
645 unsigned int b = max(0, min(255, static_cast<unsigned int>(color.b * 255.0f)));
646 unsigned int a = max(0, min(255, static_cast<unsigned int>(color.a * 255.0f)));
654 float2 SpriteBatch::StandardOrigin(float2 position, PositionUnits positionUnits, float2 renderTargetSize, float dpi)
657 if (positionUnits == PositionUnits::Pixels)
659 origin.x = (position.x / renderTargetSize.x) * 2.0f - 1.0f;
660 origin.y = 1.0f - (position.y / renderTargetSize.y) * 2.0f;
662 else if (positionUnits == PositionUnits::DIPs)
664 origin.x = ((position.x * dpi / 96.0f) / renderTargetSize.x) * 2.0f - 1.0f;
665 origin.y = 1.0f - ((position.y * dpi / 96.0f) / renderTargetSize.y) * 2.0f;
667 else if (positionUnits == PositionUnits::Normalized)
669 origin.x = position.x * 2.0f - 1.0f;
670 origin.y = 1.0f - position.y * 2.0f;
672 else if (positionUnits == PositionUnits::UniformWidth)
674 origin.x = position.x * 2.0f - 1.0f;
675 origin.y = 1.0f - position.y * (renderTargetSize.x / renderTargetSize.y) * 2.0f;
677 else if (positionUnits == PositionUnits::UniformHeight)
679 origin.x = position.x * (renderTargetSize.y / renderTargetSize.x) * 2.0f - 1.0f;
680 origin.y = 1.0f - position.y * 2.0f;
685 float2 SpriteBatch::StandardOffset(float2 size, SizeUnits sizeUnits, float2 spriteSize, float dpi)
688 if (sizeUnits == SizeUnits::Pixels)
692 else if (sizeUnits == SizeUnits::DIPs)
694 offset = size * dpi / 96.0f;
696 else if (sizeUnits == SizeUnits::Normalized)
698 offset = spriteSize * size;
703 void SpriteBatch::Draw(
704 _In_ ID3D11Texture2D* texture,
705 _In_ float2 position,
706 _In_ PositionUnits positionUnits = PositionUnits::DIPs
714 SizeUnits::Normalized
718 void SpriteBatch::Draw(
719 _In_ ID3D11Texture2D* texture,
720 _In_ float2 position,
721 _In_ PositionUnits positionUnits,
723 _In_ SizeUnits sizeUnits
732 float4(1.0f, 1.0f, 1.0f, 1.0f)
736 void SpriteBatch::Draw(
737 _In_ ID3D11Texture2D* texture,
738 _In_ float2 position,
739 _In_ PositionUnits positionUnits,
741 _In_ SizeUnits sizeUnits,
756 void SpriteBatch::Draw(
757 _In_ ID3D11Texture2D* texture,
758 _In_ float2 position,
759 _In_ PositionUnits positionUnits,
761 _In_ SizeUnits sizeUnits,
778 void SpriteBatch::Draw(
779 _In_ ID3D11Texture2D* texture,
780 _In_ float2 position,
781 _In_ PositionUnits positionUnits,
783 _In_ SizeUnits sizeUnits,
786 _In_ BlendMode blendMode
789 // Fail if drawing this sprite would exceed the capacity of the sprite batch.
790 if (m_numSpritesDrawn >= m_capacity)
792 throw ref new Platform::OutOfBoundsException();
795 // Retrieve information about the sprite.
796 TextureMapElement element = m_textureMap[texture];
797 ID3D11ShaderResourceView* textureView = element.srv.Get();
798 float2 textureSize = element.size;
799 ID3D11BlendState1* blendState = blendMode == BlendMode::Additive ? m_blendStateAdditive.Get() : m_blendStateAlpha.Get();
801 // Fail if the texture has not previously been added to the sprite batch.
802 if (textureView == nullptr)
804 throw ref new Platform::NullReferenceException();
807 // Unless this is the first sprite run, save out the previous run info if a new run is required.
809 m_numSpritesDrawn > 0 && (
810 textureView != m_currentTextureView ||
811 blendState != m_currentBlendState
815 SpriteRunInfo runInfo;
816 runInfo.textureView = m_currentTextureView;
817 runInfo.blendState = m_currentBlendState;
818 runInfo.numSprites = m_spritesInRun;
819 m_spriteRuns.push_back(runInfo);
820 m_spritesInRun = 0; // Reset for the next sprite run.
822 m_currentTextureView = textureView;
823 m_currentBlendState = blendState;
825 // Add the sprite to the buffer.
827 float2 origin = StandardOrigin(position, positionUnits, m_renderTargetSize, m_dpi);
828 float2 offset = StandardOffset(size, sizeUnits, textureSize, m_dpi);
830 if (m_technique == RenderTechnique::GeometryShader || m_technique == RenderTechnique::Instancing)
832 m_instanceData[m_numSpritesDrawn].origin = origin;
833 m_instanceData[m_numSpritesDrawn].offset = offset;
834 m_instanceData[m_numSpritesDrawn].rotation = rotation;
835 m_instanceData[m_numSpritesDrawn].color = MakeUnorm(color);
837 else if (m_technique == RenderTechnique::Replication)
841 float2(-offset.x, offset.y),
842 float2( offset.x, offset.y),
843 float2(-offset.x, -offset.y),
844 float2( offset.x, -offset.y)
847 float sinRotation = sinf(rotation);
848 float cosRotation = cosf(rotation);
850 for (int i = 0; i < 4; i++)
853 offsets[i].x * cosRotation - offsets[i].y * sinRotation,
854 offsets[i].x * sinRotation + offsets[i].y * cosRotation
856 offsets[i].x /= m_renderTargetSize.x;
857 offsets[i].y /= m_renderTargetSize.y;
860 // Write vertex buffer data.
862 ReplicationVertex* singleSpriteVertices = &m_vertexData[m_numSpritesDrawn * 4];
863 unsigned int colorUnorm = MakeUnorm(color);
864 singleSpriteVertices[0].pos = origin + offsets[0];
865 singleSpriteVertices[1].pos = origin + offsets[1];
866 singleSpriteVertices[2].pos = origin + offsets[2];
867 singleSpriteVertices[3].pos = origin + offsets[3];
868 singleSpriteVertices[0].color = colorUnorm;
869 singleSpriteVertices[1].color = colorUnorm;
870 singleSpriteVertices[2].color = colorUnorm;
871 singleSpriteVertices[3].color = colorUnorm;
872 singleSpriteVertices[0].tex = float2(0.0f, 0.0f);
873 singleSpriteVertices[1].tex = float2(1.0f, 0.0f);
874 singleSpriteVertices[2].tex = float2(0.0f, 1.0f);
875 singleSpriteVertices[3].tex = float2(1.0f, 1.0f);