Worldstone
 All Classes Files Functions Variables Enumerations Enumerator Macros Pages
DrawSprite.cpp
1 #include "DrawSprite.h"
2 #include <Platform.h>
3 #include <algorithm>
4 #include <assert.h>
5 #include <bgfx/bgfx.h>
6 #include <bx/bx.h>
7 #include <bx/math.h>
8 #include <bx/timer.h>
9 #include <limits>
10 #include "bgfxUtils.h"
11 
12 static bool screenSpaceIsTopDown = true;
13 
14 namespace
15 {
16 
17 struct PosColorTexcoordVertex
18 {
19  float m_pos[3];
20  uint32_t m_abgr;
21  float m_u;
22  float m_v;
23 
24  static void init()
25  {
26  ms_decl.begin()
27  .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
28  .add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
29  .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
30  .end();
31  }
32 
33  static bgfx::VertexDecl ms_decl;
34 };
35 
37 WS_PRAGMA_DIAGNOSTIC_IGNORED_CLANG("-Wglobal-constructors")
38 bgfx::VertexDecl PosColorTexcoordVertex::ms_decl;
40 
41 /*
42  y
43  ^ 0---2
44  | | / |
45  | 1---3
46  +-----> x
47 */
48 
49 // Assume little-endian
50 constexpr uint32_t abgrBlack = 0xFF000000;
51 constexpr uint32_t abgrWhite = 0xFFFFFFFF;
52 constexpr uint32_t abgrRed = 0x000000FF;
53 constexpr uint32_t abgrGreen = 0x0000FF00;
54 constexpr uint32_t abgrBlue = 0xFFFF0000;
55 
56 static const uint16_t s_quadTriList[] = {
57  0, 1, 2, // 0
58  1, 3, 2,
59 };
60 
61 static const uint16_t s_quadTriStrip[] = {
62  0, 1, 2, 3,
63 };
64 } // namespace
65 
67 {
68  bgfx::TextureHandle spriteTexture = BGFX_INVALID_HANDLE;
69  bgfx::VertexBufferHandle vertexBuffer = BGFX_INVALID_HANDLE;
70 };
71 
73 {
74  bgfx::IndexBufferHandle m_quadIndexBuf = BGFX_INVALID_HANDLE;
75  bgfx::ProgramHandle m_program = BGFX_INVALID_HANDLE;
76  bgfx::UniformHandle m_texColor = BGFX_INVALID_HANDLE;
77  bgfx::UniformHandle m_palColor = BGFX_INVALID_HANDLE;
78  bgfx::TextureHandle m_paletteColor = BGFX_INVALID_HANDLE;
79  int64_t m_timeOffset;
80 };
81 
82 static bgfx::VertexBufferHandle
83 createVertexBufferFromSpriteFrame(const SpriteRenderer::Frame& frame)
84 {
85  const bgfx::Memory* quadMemory = bgfx::alloc(4 * sizeof(PosColorTexcoordVertex));
86  // (void*) to shut -Wcast-align since we know bgfx will provide 4 bytes alignment
87  // Memory is actually stored right after the Memory struct, so at most 8 bytes alignment for
88  // 32bits and 16bytes for 64bit
89  PosColorTexcoordVertex* quadPtr = (PosColorTexcoordVertex*)(void*)quadMemory->data;
90 
91  // Since we need to cover the whole pixel for it to render, no need to -1
92  const float lastColumn = float(frame.width);
93  const float lastRow = float(frame.height);
94 
95  const float fOffsetX = float(frame.offsetX);
96  const float fOffsetY = float(frame.offsetY);
97  const float lastColumnOffset = fOffsetX + lastColumn;
98  const float lastRowOffset = fOffsetY + lastRow;
99  const bool topDown = true;
100  if (topDown == screenSpaceIsTopDown) {
101  quadPtr[0] = {{fOffsetX, fOffsetY, 0.f}, abgrBlack, 0.f, 0.f};
102  quadPtr[1] = {{fOffsetX, lastRowOffset, 0.f}, abgrGreen, 0.f, lastRow};
103  quadPtr[2] = {{lastColumnOffset, fOffsetY, 0.f}, abgrRed, lastColumn, 0.f};
104  quadPtr[3] = {{lastColumnOffset, lastRowOffset, 0.f}, abgrWhite, lastColumn, lastRow};
105  }
106  else
107  {
108  quadPtr[0] = {{fOffsetX, lastRowOffset, 0.f}, abgrGreen, 0.f, 0.f};
109  quadPtr[1] = {{fOffsetX, fOffsetY, 0.f}, abgrBlack, 0.f, lastRow};
110  quadPtr[2] = {{lastColumnOffset, lastRowOffset, 0.f}, abgrWhite, lastColumn, 0.f};
111  quadPtr[3] = {{lastColumnOffset, fOffsetY, 0.f}, abgrRed, lastColumn, lastRow};
112  }
113 
114  // Create static vertex buffer.
115  return bgfx::createVertexBuffer(quadMemory, PosColorTexcoordVertex::ms_decl);
116 }
117 
118 static bgfx::TextureHandle createTextureFromSprite(const SpriteRenderer::Frame& frame)
119 {
120  bgfx::TextureHandle spriteTexture = BGFX_INVALID_HANDLE;
121  uint32_t flagsSprite = BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT;
122  spriteTexture = bgfx::createTexture2D(
123  frame.width, frame.height, false, 1, bgfx::TextureFormat::R8U, flagsSprite,
124  bgfx::copy(frame.data, uint32_t(frame.width) * uint32_t(frame.height)));
125  if (bgfx::isValid(spriteTexture)) bgfx::setName(spriteTexture, "SpriteTexture");
126  return spriteTexture;
127 }
128 
129 static FrameRenderData createFrameRenderData(const SpriteRenderer::Frame& frame)
130 {
131  FrameRenderData renderData;
132  renderData.spriteTexture = createTextureFromSprite(frame);
133  renderData.vertexBuffer = createVertexBufferFromSpriteFrame(frame);
134  return renderData;
135 }
136 static void destroy(FrameRenderData& frameRenderData)
137 {
138  bgfx::destroy(frameRenderData.vertexBuffer);
139  bgfx::destroy(frameRenderData.spriteTexture);
140 }
141 
142 void SpriteRenderer::SpriteRenderData::addSpriteFrame(const Frame& frame)
143 {
144  framesData.push_back(createFrameRenderData(frame));
145 }
146 SpriteRenderer::SpriteRenderData::~SpriteRenderData()
147 {
148  for (FrameRenderData& frameRenderData : framesData)
149  destroy(frameRenderData);
150 }
151 
152 void SpriteRenderer::init(const WorldStone::Palette& palette)
153 {
154  data = std::make_unique<SpriteRendererData>();
155 
156  BX_UNUSED(s_quadTriList, s_quadTriStrip);
157  BX_UNUSED(abgrBlack, abgrWhite, abgrRed, abgrGreen, abgrBlue);
158  // Create vertex stream declaration.
159  PosColorTexcoordVertex::init();
160 
161  // Create static index buffer.
162  data->m_quadIndexBuf = bgfx::createIndexBuffer(
163  // Static data can be passed with bgfx::makeRef
164  bgfx::makeRef(s_quadTriStrip, sizeof(s_quadTriStrip)));
165 
166  // Create the samplers
167  data->m_texColor = bgfx::createUniform("s_texColor", bgfx::UniformType::Int1);
168  data->m_palColor = bgfx::createUniform("s_palColor", bgfx::UniformType::Int1);
169 
170  setPalette(palette);
171 
172  // Create program from shaders.
173  data->m_program = loadProgram("vs_sprite", "fs_sprite");
174  data->m_timeOffset = bx::getHPCounter();
175 }
176 
177 int SpriteRenderer::shutdown()
178 {
179  spritesToRender.clear();
180  spritesData.clear();
181  // Cleanup.
182  if (data) {
183  bgfx::destroy(data->m_paletteColor);
184  bgfx::destroy(data->m_palColor);
185  bgfx::destroy(data->m_texColor);
186  bgfx::destroy(data->m_program);
187  bgfx::destroy(data->m_quadIndexBuf);
188  }
189 
190  return 0;
191 }
192 
193 void SpriteRenderer::setPalette(const WorldStone::Palette& palette)
194 {
195  if (bgfx::isValid(data->m_paletteColor)) bgfx::destroy(data->m_paletteColor);
196 
197  using Palette = WorldStone::Palette;
198  static_assert(sizeof(Palette::Color24Bits) == 3, "");
199  auto paletteRGB888 = bgfx::alloc(sizeof(Palette::Color24Bits) * Palette::colorCount);
200  for (size_t i = 0; i < Palette::colorCount; i++)
201  {
202  const WorldStone::Palette::Color color = palette.colors[i];
203  ((WorldStone::Palette::Color24Bits*)paletteRGB888->data)[i] = {color.r, color.g, color.b};
204  }
205  data->m_paletteColor =
206  bgfx::createTexture2D(Palette::colorCount, 1, false, 1, bgfx::TextureFormat::RGB8,
207  BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT, paletteRGB888);
208  bgfx::setName(data->m_paletteColor, "SpritePalette");
209 }
210 
211 SpriteRenderer::SpriteRenderDataHandle SpriteRenderer::createSpriteRenderData()
212 {
213  spritesData.push_back(std::make_shared<SpriteRenderData>());
214  return spritesData.back();
215 }
216 
217 void SpriteRenderer::destroySpriteRenderData(SpriteRenderDataHandle renderDataHandle)
218 {
219  SpriteRenderData* renderDataPtr = renderDataHandle.lock().get();
220  assert(renderDataPtr != nullptr);
221  for (auto& ptr : spritesData)
222  {
223  if (ptr.get() == renderDataPtr) {
224  assert(ptr.unique());
225  ptr = nullptr;
226  return;
227  }
228  }
229 }
230 
231 void SpriteRenderer::recycleSpritesData()
232 {
233  // This is really aggressive but right now we ask the user to always check/recreate the data
234  // when needed All the handle system has to be written (and more than anything, we have to get
235  // rid of shared_ptr)
236  if (spritesData.size() > 20) {
237  spritesData.erase(std::remove_if(spritesData.begin(), spritesData.end(),
238  [](const std::shared_ptr<SpriteRenderData>& ptr) {
239  return ptr == nullptr || ptr.unique();
240  }),
241  spritesData.end());
242  }
243 }
244 
245 void SpriteRenderer::pushDrawRequest(SpriteRenderDataHandle spriteHandle,
246  const DrawRequest& drawRequest)
247 {
248  auto spritePtr = spriteHandle.lock();
249  assert(spritePtr != nullptr);
250  spritesToRender.emplace_back(std::make_pair(std::move(spritePtr), drawRequest));
251 }
252 
253 void SpriteRenderer::drawFrame(const FrameRenderData& renderData)
254 {
255  // Set vertex and index buffer.
256  bgfx::setVertexBuffer(0, renderData.vertexBuffer);
257  bgfx::setIndexBuffer(data->m_quadIndexBuf);
258 
259  // Bind texture
260  bgfx::setTexture(0, data->m_texColor, renderData.spriteTexture);
261  bgfx::setTexture(1, data->m_palColor, data->m_paletteColor);
262 
263  // Set render states.
264  // No ZBuffer usage for now, it is 2d... Perhaps it'll be used for layers later ?
265  bgfx::setState(0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_CULL_CW
266  | BGFX_STATE_PT_TRISTRIP);
267 
268  // Submit primitive for rendering to view 0.
269  bgfx::submit(0, data->m_program);
270 }
271 
272 bool SpriteRenderer::draw(int screenWidth, int screenHeight)
273 {
274  float at[3] = {0.f, 0.f, 0.f};
275  float eye[3] = {0.f, 0.f, -35.0f};
276 
277  // Set view and projection matrix for view 0.
278  float view[16];
279  bx::mtxLookAtRh(view, eye, at);
280 
281  float proj[16];
282  if (screenSpaceIsTopDown) {
283 
284  // RightHanded origin at bottom left, z coming from the screen
285  //
286  // _ +Z
287  // /|
288  // /
289  // /
290  // +----------> +X
291  // |
292  // |
293  // |
294  // |
295  // \/
296  // +Y
297  //
298  //
299  bx::mtxOrtho(proj, 0.f, float(screenWidth), float(screenHeight), 0.f, -1000.f, 1000.f, 0.f,
300  bgfx::getCaps()->homogeneousDepth);
301  }
302  else
303  {
304 
305  // RightHanded origin at bottom left, z coming from the screen
306  //
307  // +Y
308  // ^
309  // |
310  // |
311  // |
312  // |
313  // +----------> +X
314  // /
315  // /
316  // +Z
317  bx::mtxOrtho(proj, 0.f, float(screenWidth), 0.f, float(screenHeight), 1000.f, -1000.f, 0.f,
318  bgfx::getCaps()->homogeneousDepth);
319  }
320 
321  bgfx::setViewTransform(0, nullptr, proj);
322 
323  // Set view 0 default viewport.
324  bgfx::setViewRect(0, 0, 0, uint16_t(screenWidth), uint16_t(screenHeight));
325 
326  // This dummy draw call is here to make sure that view 0 is cleared
327  // if no other draw calls are submitted to view 0.
328  bgfx::touch(0);
329 
330  // i is just a debug, will disappear later
331  static size_t i = size_t(-1);
332  for (const auto& spriteData : spritesToRender)
333  {
334  i++;
335  i %= (spriteData.first->framesData.size() * 10);
336  const DrawRequest& drawRequest = spriteData.second;
337  const FrameRenderData& renderData = spriteData.first->framesData[drawRequest.frame];
338  const float scale = drawRequest.scale;
339 
340  // Submit 1 quad
341  float mtx[16];
342  bx::mtxIdentity(mtx);
343  bx::mtxScale(mtx, scale);
344  // Translation
345  const auto& translation = drawRequest.translation;
346  mtx[12] = translation.x;
347  mtx[13] = translation.y;
348  mtx[14] = 0.f;
349 
350  bgfx::setTransform(&mtx);
351 
352  drawFrame(renderData);
353  }
354 
355  spritesToRender.clear();
356  recycleSpritesData();
357  return true;
358 }
359 
360 SpriteRenderer::SpriteRenderer() {}
361 SpriteRenderer::~SpriteRenderer() {}
#define WS_PRAGMA_DIAGNOSTIC_POP()
Restores the previous state of the compiler warnings.
Definition: Platform.h:94
#define WS_PRAGMA_DIAGNOSTIC_IGNORED_CLANG(_x)
Silence clang-specific warnings.
Definition: Platform.h:97
#define WS_PRAGMA_DIAGNOSTIC_PUSH()
Saves the current state of the compiler warnings.
Definition: Platform.h:93
Platform specific tools and macros.
Helper to load a Diablo 2 palette (.pal/.dat format)
Definition: Palette.h:21