1 | |
2 | //////////////////////////////////////////////////////////// |
3 | // Headers |
4 | //////////////////////////////////////////////////////////// |
5 | #define STB_PERLIN_IMPLEMENTATION |
6 | #include "stb_perlin.h" |
7 | #include <SFML/Graphics.hpp> |
8 | #include <vector> |
9 | #include <deque> |
10 | #include <sstream> |
11 | #include <algorithm> |
12 | #include <cstring> |
13 | #include <cmath> |
14 | |
15 | |
16 | namespace |
17 | { |
18 | // Width and height of the application window |
19 | const unsigned int windowWidth = 800; |
20 | const unsigned int windowHeight = 600; |
21 | |
22 | // Resolution of the generated terrain |
23 | const unsigned int resolutionX = 800; |
24 | const unsigned int resolutionY = 600; |
25 | |
26 | // Thread pool parameters |
27 | const unsigned int threadCount = 4; |
28 | const unsigned int blockCount = 32; |
29 | |
30 | struct WorkItem |
31 | { |
32 | sf::Vertex* targetBuffer; |
33 | unsigned int index; |
34 | }; |
35 | |
36 | std::deque<WorkItem> workQueue; |
37 | std::vector<sf::Thread*> threads; |
38 | int pendingWorkCount = 0; |
39 | bool workPending = true; |
40 | bool bufferUploadPending = false; |
41 | sf::Mutex workQueueMutex; |
42 | |
43 | struct Setting |
44 | { |
45 | const char* name; |
46 | float* value; |
47 | }; |
48 | |
49 | // Terrain noise parameters |
50 | const int perlinOctaves = 3; |
51 | |
52 | float perlinFrequency = 7.0f; |
53 | float perlinFrequencyBase = 4.0f; |
54 | |
55 | // Terrain generation parameters |
56 | float heightBase = 0.0f; |
57 | float edgeFactor = 0.9f; |
58 | float edgeDropoffExponent = 1.5f; |
59 | |
60 | float snowcapHeight = 0.6f; |
61 | |
62 | // Terrain lighting parameters |
63 | float heightFactor = windowHeight / 2.0f; |
64 | float heightFlatten = 3.0f; |
65 | float lightFactor = 0.7f; |
66 | } |
67 | |
68 | |
69 | // Forward declarations of the functions we define further down |
70 | void threadFunction(); |
71 | void generateTerrain(sf::Vertex* vertexBuffer); |
72 | |
73 | |
74 | //////////////////////////////////////////////////////////// |
75 | /// Entry point of application |
76 | /// |
77 | /// \return Application exit code |
78 | /// |
79 | //////////////////////////////////////////////////////////// |
80 | int main() |
81 | { |
82 | // Create the window of the application |
83 | sf::RenderWindow window(sf::VideoMode(windowWidth, windowHeight), "SFML Island" , |
84 | sf::Style::Titlebar | sf::Style::Close); |
85 | window.setVerticalSyncEnabled(true); |
86 | |
87 | sf::Font font; |
88 | if (!font.loadFromFile("resources/sansation.ttf" )) |
89 | return EXIT_FAILURE; |
90 | |
91 | // Create all of our graphics resources |
92 | sf::Text hudText; |
93 | sf::Text statusText; |
94 | sf::Shader terrainShader; |
95 | sf::RenderStates terrainStates(&terrainShader); |
96 | sf::VertexBuffer terrain(sf::Triangles, sf::VertexBuffer::Static); |
97 | |
98 | // Set up our text drawables |
99 | statusText.setFont(font); |
100 | statusText.setCharacterSize(28); |
101 | statusText.setFillColor(sf::Color::White); |
102 | statusText.setOutlineColor(sf::Color::Black); |
103 | statusText.setOutlineThickness(2.0f); |
104 | |
105 | hudText.setFont(font); |
106 | hudText.setCharacterSize(14); |
107 | hudText.setFillColor(sf::Color::White); |
108 | hudText.setOutlineColor(sf::Color::Black); |
109 | hudText.setOutlineThickness(2.0f); |
110 | hudText.setPosition(5.0f, 5.0f); |
111 | |
112 | // Staging buffer for our terrain data that we will upload to our VertexBuffer |
113 | std::vector<sf::Vertex> terrainStagingBuffer; |
114 | |
115 | // Check whether the prerequisites are suppprted |
116 | bool prerequisitesSupported = sf::VertexBuffer::isAvailable() && sf::Shader::isAvailable(); |
117 | |
118 | // Set up our graphics resources and set the status text accordingly |
119 | if (!prerequisitesSupported) |
120 | { |
121 | statusText.setString("Shaders and/or Vertex Buffers Unsupported" ); |
122 | } |
123 | else if (!terrainShader.loadFromFile("resources/terrain.vert" , "resources/terrain.frag" )) |
124 | { |
125 | prerequisitesSupported = false; |
126 | |
127 | statusText.setString("Failed to load shader program" ); |
128 | } |
129 | else |
130 | { |
131 | // Start up our thread pool |
132 | for (unsigned int i = 0; i < threadCount; i++) |
133 | { |
134 | threads.push_back(new sf::Thread(threadFunction)); |
135 | threads.back()->launch(); |
136 | } |
137 | |
138 | // Create our VertexBuffer with enough space to hold all the terrain geometry |
139 | terrain.create(resolutionX * resolutionY * 6); |
140 | |
141 | // Resize the staging buffer to be able to hold all the terrain geometry |
142 | terrainStagingBuffer.resize(resolutionX * resolutionY * 6); |
143 | |
144 | // Generate the initial terrain |
145 | generateTerrain(&terrainStagingBuffer[0]); |
146 | |
147 | statusText.setString("Generating Terrain..." ); |
148 | } |
149 | |
150 | // Center the status text |
151 | statusText.setPosition((windowWidth - statusText.getLocalBounds().width) / 2.f, (windowHeight - statusText.getLocalBounds().height) / 2.f); |
152 | |
153 | // Set up an array of pointers to our settings for arrow navigation |
154 | Setting settings[] = |
155 | { |
156 | {"perlinFrequency" , &perlinFrequency}, |
157 | {"perlinFrequencyBase" , &perlinFrequencyBase}, |
158 | {"heightBase" , &heightBase}, |
159 | {"edgeFactor" , &edgeFactor}, |
160 | {"edgeDropoffExponent" , &edgeDropoffExponent}, |
161 | {"snowcapHeight" , &snowcapHeight}, |
162 | {"heightFactor" , &heightFactor}, |
163 | {"heightFlatten" , &heightFlatten}, |
164 | {"lightFactor" , &lightFactor} |
165 | }; |
166 | |
167 | const int settingCount = 9; |
168 | int currentSetting = 0; |
169 | |
170 | std::ostringstream osstr; |
171 | sf::Clock clock; |
172 | |
173 | while (window.isOpen()) |
174 | { |
175 | // Handle events |
176 | sf::Event event; |
177 | while (window.pollEvent(event)) |
178 | { |
179 | // Window closed or escape key pressed: exit |
180 | if ((event.type == sf::Event::Closed) || |
181 | ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape))) |
182 | { |
183 | window.close(); |
184 | break; |
185 | } |
186 | |
187 | // Arrow key pressed: |
188 | if (prerequisitesSupported && (event.type == sf::Event::KeyPressed)) |
189 | { |
190 | switch (event.key.code) |
191 | { |
192 | case sf::Keyboard::Return: generateTerrain(&terrainStagingBuffer[0]); break; |
193 | case sf::Keyboard::Down: currentSetting = (currentSetting + 1) % settingCount; break; |
194 | case sf::Keyboard::Up: currentSetting = (currentSetting + settingCount - 1) % settingCount; break; |
195 | case sf::Keyboard::Left: *(settings[currentSetting].value) -= 0.1f; break; |
196 | case sf::Keyboard::Right: *(settings[currentSetting].value) += 0.1f; break; |
197 | default: break; |
198 | } |
199 | } |
200 | } |
201 | |
202 | // Clear, draw graphics objects and display |
203 | window.clear(); |
204 | |
205 | window.draw(statusText); |
206 | |
207 | if (prerequisitesSupported) |
208 | { |
209 | { |
210 | sf::Lock lock(workQueueMutex); |
211 | |
212 | // Don't bother updating/drawing the VertexBuffer while terrain is being regenerated |
213 | if (!pendingWorkCount) |
214 | { |
215 | // If there is new data pending to be uploaded to the VertexBuffer, do it now |
216 | if (bufferUploadPending) |
217 | { |
218 | terrain.update(&terrainStagingBuffer[0]); |
219 | bufferUploadPending = false; |
220 | } |
221 | |
222 | terrainShader.setUniform("lightFactor" , lightFactor); |
223 | window.draw(terrain, terrainStates); |
224 | } |
225 | } |
226 | |
227 | // Update and draw the HUD text |
228 | osstr.str("" ); |
229 | osstr << "Frame: " << clock.restart().asMilliseconds() << "ms\n" |
230 | << "perlinOctaves: " << perlinOctaves << "\n\n" |
231 | << "Use the arrow keys to change the values.\nUse the return key to regenerate the terrain.\n\n" ; |
232 | |
233 | for (int i = 0; i < settingCount; ++i) |
234 | osstr << ((i == currentSetting) ? ">> " : " " ) << settings[i].name << ": " << *(settings[i].value) << "\n" ; |
235 | |
236 | hudText.setString(osstr.str()); |
237 | |
238 | window.draw(hudText); |
239 | } |
240 | |
241 | // Display things on screen |
242 | window.display(); |
243 | } |
244 | |
245 | // Shut down our thread pool |
246 | { |
247 | sf::Lock lock(workQueueMutex); |
248 | workPending = false; |
249 | } |
250 | |
251 | while (!threads.empty()) |
252 | { |
253 | threads.back()->wait(); |
254 | delete threads.back(); |
255 | threads.pop_back(); |
256 | } |
257 | |
258 | return EXIT_SUCCESS; |
259 | } |
260 | |
261 | |
262 | //////////////////////////////////////////////////////////// |
263 | /// Get the terrain elevation at the given coordinates. |
264 | /// |
265 | //////////////////////////////////////////////////////////// |
266 | float getElevation(float x, float y) |
267 | { |
268 | x = x / resolutionX - 0.5f; |
269 | y = y / resolutionY - 0.5f; |
270 | |
271 | float elevation = 0.0f; |
272 | |
273 | for (int i = 0; i < perlinOctaves; i++) |
274 | { |
275 | elevation += stb_perlin_noise3( |
276 | x * perlinFrequency * std::pow(perlinFrequencyBase, i), |
277 | y * perlinFrequency * std::pow(perlinFrequencyBase, i), |
278 | 0, 0, 0, 0 |
279 | ) * std::pow(perlinFrequencyBase, -i); |
280 | } |
281 | |
282 | elevation = (elevation + 1.f) / 2.f; |
283 | |
284 | float distance = 2.0f * std::sqrt(x * x + y * y); |
285 | elevation = (elevation + heightBase) * (1.0f - edgeFactor * std::pow(distance, edgeDropoffExponent)); |
286 | elevation = std::min(std::max(elevation, 0.0f), 1.0f); |
287 | |
288 | return elevation; |
289 | } |
290 | |
291 | |
292 | //////////////////////////////////////////////////////////// |
293 | /// Get the terrain moisture at the given coordinates. |
294 | /// |
295 | //////////////////////////////////////////////////////////// |
296 | float getMoisture(float x, float y) |
297 | { |
298 | x = x / resolutionX - 0.5f; |
299 | y = y / resolutionY - 0.5f; |
300 | |
301 | float moisture = stb_perlin_noise3( |
302 | x * 4.f + 0.5f, |
303 | y * 4.f + 0.5f, |
304 | 0, 0, 0, 0 |
305 | ); |
306 | |
307 | return (moisture + 1.f) / 2.f; |
308 | } |
309 | |
310 | |
311 | //////////////////////////////////////////////////////////// |
312 | /// Get the lowlands terrain color for the given moisture. |
313 | /// |
314 | //////////////////////////////////////////////////////////// |
315 | sf::Color getLowlandsTerrainColor(float moisture) |
316 | { |
317 | sf::Color color = |
318 | moisture < 0.27f ? sf::Color(240, 240, 180) : |
319 | moisture < 0.3f ? sf::Color(240 - 240 * (moisture - 0.27f) / 0.03f, 240 - 40 * (moisture - 0.27f) / 0.03f, 180 - 180 * (moisture - 0.27f) / 0.03f) : |
320 | moisture < 0.4f ? sf::Color(0, 200, 0) : |
321 | moisture < 0.48f ? sf::Color(0, 200 - 40 * (moisture - 0.4f) / 0.08f, 0) : |
322 | moisture < 0.6f ? sf::Color(0, 160, 0) : |
323 | moisture < 0.7f ? sf::Color(34 * (moisture - 0.6f) / 0.1f, 160 - 60 * (moisture - 0.6f) / 0.1f, 34 * (moisture - 0.6f) / 0.1f) : |
324 | sf::Color(34, 100, 34); |
325 | |
326 | return color; |
327 | } |
328 | |
329 | |
330 | //////////////////////////////////////////////////////////// |
331 | /// Get the highlands terrain color for the given elevation |
332 | /// and moisture. |
333 | /// |
334 | //////////////////////////////////////////////////////////// |
335 | sf::Color getHighlandsTerrainColor(float elevation, float moisture) |
336 | { |
337 | sf::Color lowlandsColor = getLowlandsTerrainColor(moisture); |
338 | |
339 | sf::Color color = |
340 | moisture < 0.6f ? sf::Color(112, 128, 144) : |
341 | sf::Color(112 + 110 * (moisture - 0.6f) / 0.4f, 128 + 56 * (moisture - 0.6f) / 0.4f, 144 - 9 * (moisture - 0.6f) / 0.4f); |
342 | |
343 | float factor = std::min((elevation - 0.4f) / 0.1f, 1.f); |
344 | |
345 | color.r = lowlandsColor.r * (1.f - factor) + color.r * factor; |
346 | color.g = lowlandsColor.g * (1.f - factor) + color.g * factor; |
347 | color.b = lowlandsColor.b * (1.f - factor) + color.b * factor; |
348 | |
349 | return color; |
350 | } |
351 | |
352 | |
353 | //////////////////////////////////////////////////////////// |
354 | /// Get the snowcap terrain color for the given elevation |
355 | /// and moisture. |
356 | /// |
357 | //////////////////////////////////////////////////////////// |
358 | sf::Color getSnowcapTerrainColor(float elevation, float moisture) |
359 | { |
360 | sf::Color highlandsColor = getHighlandsTerrainColor(elevation, moisture); |
361 | |
362 | sf::Color color = sf::Color::White; |
363 | |
364 | float factor = std::min((elevation - snowcapHeight) / 0.05f, 1.f); |
365 | |
366 | color.r = highlandsColor.r * (1.f - factor) + color.r * factor; |
367 | color.g = highlandsColor.g * (1.f - factor) + color.g * factor; |
368 | color.b = highlandsColor.b * (1.f - factor) + color.b * factor; |
369 | |
370 | return color; |
371 | } |
372 | |
373 | |
374 | //////////////////////////////////////////////////////////// |
375 | /// Get the terrain color for the given elevation and |
376 | /// moisture. |
377 | /// |
378 | //////////////////////////////////////////////////////////// |
379 | sf::Color getTerrainColor(float elevation, float moisture) |
380 | { |
381 | sf::Color color = |
382 | elevation < 0.11f ? sf::Color(0, 0, elevation / 0.11f * 74.f + 181.0f) : |
383 | elevation < 0.14f ? sf::Color(std::pow((elevation - 0.11f) / 0.03f, 0.3f) * 48.f, std::pow((elevation - 0.11f) / 0.03f, 0.3f) * 48.f, 255) : |
384 | elevation < 0.16f ? sf::Color((elevation - 0.14f) * 128.f / 0.02f + 48.f, (elevation - 0.14f) * 128.f / 0.02f + 48.f, 127.0f + (0.16f - elevation) * 128.f / 0.02f) : |
385 | elevation < 0.17f ? sf::Color(240, 230, 140) : |
386 | elevation < 0.4f ? getLowlandsTerrainColor(moisture) : |
387 | elevation < snowcapHeight ? getHighlandsTerrainColor(elevation, moisture) : |
388 | getSnowcapTerrainColor(elevation, moisture); |
389 | |
390 | return color; |
391 | } |
392 | |
393 | |
394 | //////////////////////////////////////////////////////////// |
395 | /// Compute a compressed representation of the surface |
396 | /// normal based on the given coordinates, and the elevation |
397 | /// of the 4 adjacent neighbours. |
398 | /// |
399 | //////////////////////////////////////////////////////////// |
400 | sf::Vector2f computeNormal(int x, int y, float left, float right, float bottom, float top) |
401 | { |
402 | sf::Vector3f deltaX(1, 0, (std::pow(right, heightFlatten) - std::pow(left, heightFlatten)) * heightFactor); |
403 | sf::Vector3f deltaY(0, 1, (std::pow(top, heightFlatten) - std::pow(bottom, heightFlatten)) * heightFactor); |
404 | |
405 | sf::Vector3f crossProduct( |
406 | deltaX.y * deltaY.z - deltaX.z * deltaY.y, |
407 | deltaX.z * deltaY.x - deltaX.x * deltaY.z, |
408 | deltaX.x * deltaY.y - deltaX.y * deltaY.x |
409 | ); |
410 | |
411 | // Scale cross product to make z component 1.0f so we can drop it |
412 | crossProduct /= crossProduct.z; |
413 | |
414 | // Return "compressed" normal |
415 | return sf::Vector2f(crossProduct.x, crossProduct.y); |
416 | } |
417 | |
418 | |
419 | //////////////////////////////////////////////////////////// |
420 | /// Process a terrain generation work item. Use the vector |
421 | /// of vertices as scratch memory and upload the data to |
422 | /// the vertex buffer when done. |
423 | /// |
424 | //////////////////////////////////////////////////////////// |
425 | void processWorkItem(std::vector<sf::Vertex>& vertices, const WorkItem& workItem) |
426 | { |
427 | unsigned int rowBlockSize = (resolutionY / blockCount) + 1; |
428 | unsigned int rowStart = rowBlockSize * workItem.index; |
429 | |
430 | if (rowStart >= resolutionY) |
431 | return; |
432 | |
433 | unsigned int rowEnd = std::min(rowStart + rowBlockSize, resolutionY); |
434 | unsigned int rowCount = rowEnd - rowStart; |
435 | |
436 | const float scalingFactorX = static_cast<float>(windowWidth) / static_cast<float>(resolutionX); |
437 | const float scalingFactorY = static_cast<float>(windowHeight) / static_cast<float>(resolutionY); |
438 | |
439 | for (unsigned int y = rowStart; y < rowEnd; y++) |
440 | { |
441 | for (int x = 0; x < resolutionX; x++) |
442 | { |
443 | int arrayIndexBase = ((y - rowStart) * resolutionX + x) * 6; |
444 | |
445 | // Top left corner (first triangle) |
446 | if (x > 0) |
447 | { |
448 | vertices[arrayIndexBase + 0] = vertices[arrayIndexBase - 6 + 5]; |
449 | } |
450 | else if (y > rowStart) |
451 | { |
452 | vertices[arrayIndexBase + 0] = vertices[arrayIndexBase - resolutionX * 6 + 1]; |
453 | } |
454 | else |
455 | { |
456 | vertices[arrayIndexBase + 0].position = sf::Vector2f(x * scalingFactorX, y * scalingFactorY); |
457 | vertices[arrayIndexBase + 0].color = getTerrainColor(getElevation(x, y), getMoisture(x, y)); |
458 | vertices[arrayIndexBase + 0].texCoords = computeNormal(x, y, getElevation(x - 1, y), getElevation(x + 1, y), getElevation(x, y + 1), getElevation(x, y - 1)); |
459 | } |
460 | |
461 | // Bottom left corner (first triangle) |
462 | if (x > 0) |
463 | { |
464 | vertices[arrayIndexBase + 1] = vertices[arrayIndexBase - 6 + 2]; |
465 | } |
466 | else |
467 | { |
468 | vertices[arrayIndexBase + 1].position = sf::Vector2f(x * scalingFactorX, (y + 1) * scalingFactorY); |
469 | vertices[arrayIndexBase + 1].color = getTerrainColor(getElevation(x, y + 1), getMoisture(x, y + 1)); |
470 | vertices[arrayIndexBase + 1].texCoords = computeNormal(x, y + 1, getElevation(x - 1, y + 1), getElevation(x + 1, y + 1), getElevation(x, y + 2), getElevation(x, y)); |
471 | } |
472 | |
473 | // Bottom right corner (first triangle) |
474 | vertices[arrayIndexBase + 2].position = sf::Vector2f((x + 1) * scalingFactorX, (y + 1) * scalingFactorY); |
475 | vertices[arrayIndexBase + 2].color = getTerrainColor(getElevation(x + 1, y + 1), getMoisture(x + 1, y + 1)); |
476 | vertices[arrayIndexBase + 2].texCoords = computeNormal(x + 1, y + 1, getElevation(x, y + 1), getElevation(x + 2, y + 1), getElevation(x + 1, y + 2), getElevation(x + 1, y)); |
477 | |
478 | // Top left corner (second triangle) |
479 | vertices[arrayIndexBase + 3] = vertices[arrayIndexBase + 0]; |
480 | |
481 | // Bottom right corner (second triangle) |
482 | vertices[arrayIndexBase + 4] = vertices[arrayIndexBase + 2]; |
483 | |
484 | // Top right corner (second triangle) |
485 | if (y > rowStart) |
486 | { |
487 | vertices[arrayIndexBase + 5] = vertices[arrayIndexBase - resolutionX * 6 + 2]; |
488 | } |
489 | else |
490 | { |
491 | vertices[arrayIndexBase + 5].position = sf::Vector2f((x + 1) * scalingFactorX, y * scalingFactorY); |
492 | vertices[arrayIndexBase + 5].color = getTerrainColor(getElevation(x + 1, y), getMoisture(x + 1, y)); |
493 | vertices[arrayIndexBase + 5].texCoords = computeNormal(x + 1, y, getElevation(x, y), getElevation(x + 2, y), getElevation(x + 1, y + 1), getElevation(x + 1, y - 1)); |
494 | } |
495 | } |
496 | } |
497 | |
498 | // Copy the resulting geometry from our thread-local buffer into the target buffer |
499 | std::memcpy(workItem.targetBuffer + (resolutionX * rowStart * 6), &vertices[0], sizeof(sf::Vertex) * resolutionX * rowCount * 6); |
500 | } |
501 | |
502 | |
503 | //////////////////////////////////////////////////////////// |
504 | /// Worker thread entry point. We use a thread pool to avoid |
505 | /// the heavy cost of constantly recreating and starting |
506 | /// new threads whenever we need to regenerate the terrain. |
507 | /// |
508 | //////////////////////////////////////////////////////////// |
509 | void threadFunction() |
510 | { |
511 | unsigned int rowBlockSize = (resolutionY / blockCount) + 1; |
512 | |
513 | std::vector<sf::Vertex> vertices(resolutionX * rowBlockSize * 6); |
514 | |
515 | WorkItem workItem = {0, 0}; |
516 | |
517 | // Loop until the application exits |
518 | for (;;) |
519 | { |
520 | workItem.targetBuffer = 0; |
521 | |
522 | // Check if there are new work items in the queue |
523 | { |
524 | sf::Lock lock(workQueueMutex); |
525 | |
526 | if (!workPending) |
527 | return; |
528 | |
529 | if (!workQueue.empty()) |
530 | { |
531 | workItem = workQueue.front(); |
532 | workQueue.pop_front(); |
533 | } |
534 | } |
535 | |
536 | // If we didn't receive a new work item, keep looping |
537 | if (!workItem.targetBuffer) |
538 | { |
539 | sf::sleep(sf::milliseconds(10)); |
540 | |
541 | continue; |
542 | } |
543 | |
544 | processWorkItem(vertices, workItem); |
545 | |
546 | { |
547 | sf::Lock lock(workQueueMutex); |
548 | |
549 | --pendingWorkCount; |
550 | } |
551 | } |
552 | } |
553 | |
554 | |
555 | //////////////////////////////////////////////////////////// |
556 | /// Terrain generation entry point. This queues up the |
557 | /// generation work items which the worker threads dequeue |
558 | /// and process. |
559 | /// |
560 | //////////////////////////////////////////////////////////// |
561 | void generateTerrain(sf::Vertex* buffer) |
562 | { |
563 | bufferUploadPending = true; |
564 | |
565 | // Make sure the work queue is empty before queuing new work |
566 | for (;;) |
567 | { |
568 | { |
569 | sf::Lock lock(workQueueMutex); |
570 | |
571 | if (workQueue.empty()) |
572 | break; |
573 | } |
574 | |
575 | sf::sleep(sf::milliseconds(10)); |
576 | } |
577 | |
578 | // Queue all the new work items |
579 | { |
580 | sf::Lock lock(workQueueMutex); |
581 | |
582 | for (unsigned int i = 0; i < blockCount; i++) |
583 | { |
584 | WorkItem workItem = {buffer, i}; |
585 | workQueue.push_back(workItem); |
586 | } |
587 | |
588 | pendingWorkCount = blockCount; |
589 | } |
590 | } |
591 | |