KiCad PCB EDA Suite
Loading...
Searching...
No Matches
cached_container_gpu.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright 2013-2017 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Maciej Suminski <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
27#include <gal/opengl/shader.h>
28#include <gal/opengl/utils.h>
29
30#include <wx/log.h>
31
32#include <cstring>
33#include <list>
34#include <memory>
35
36#include <core/profile.h>
37#include <trace_helpers.h>
38
39using namespace KIGFX;
40
48static const wxChar* const traceGalCachedContainerGpu = wxT( "KICAD_GAL_CACHED_CONTAINER_GPU" );
49
50
52 CACHED_CONTAINER( aSize ),
53 m_isMapped( false ),
55{
56 m_useCopyBuffer = !!GLAD_GL_ARB_copy_buffer;
57
58 wxString vendor( glGetString( GL_VENDOR ) );
59
60 // workaround for intel GPU drivers:
61 // disable glCopyBuffer, causes crashes/freezes on certain driver versions
62 // Note, Intel's GL_VENDOR string varies depending on GPU/driver generation
63 // But generally always starts with Intel at least
64 if( vendor.StartsWith( "Intel" ) || vendor.Contains( "etnaviv" ) )
65 {
66 m_useCopyBuffer = false;
67 }
68
69#ifdef KICAD_GAL_PROFILE
70 wxLogTrace( traceGalProfile, "VBO initial size: %u", m_currentSize );
71#endif
72
73 glGenBuffers( 1, &m_glBufferHandle );
74 glBindBuffer( GL_ARRAY_BUFFER, m_glBufferHandle );
75 glBufferData( GL_ARRAY_BUFFER, m_currentSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
76 glBindBuffer( GL_ARRAY_BUFFER, 0 );
77 checkGlError( "allocating video memory for cached container", __FILE__, __LINE__ );
78}
79
80
82{
83 if( m_isMapped )
84 Unmap();
85
86 if( glDeleteBuffers )
87 glDeleteBuffers( 1, &m_glBufferHandle );
88}
89
90
92{
93 wxCHECK( !IsMapped(), /*void*/ );
94
95 // OpenGL version might suddenly stop being available in Windows when an RDP session is started
96 if( !glBindBuffer )
97 throw std::runtime_error( "OpenGL no longer available!" );
98
99 glBindBuffer( GL_ARRAY_BUFFER, m_glBufferHandle );
100 m_vertices = static_cast<VERTEX*>( glMapBuffer( GL_ARRAY_BUFFER, GL_READ_WRITE ) );
101
102 if( checkGlError( "mapping vertices buffer", __FILE__, __LINE__ ) == GL_NO_ERROR
103 && m_vertices != nullptr )
104 {
105 m_isMapped = true;
106 }
107 else
108 {
109 m_vertices = nullptr;
110 glBindBuffer( GL_ARRAY_BUFFER, 0 );
111 throw std::runtime_error( "Could not map vertex buffer: glMapBuffer returned null" );
112 }
113}
114
115
117{
118 wxCHECK( IsMapped(), /*void*/ );
119
120 // This gets called from ~CACHED_CONTAINER_GPU. To avoid throwing an exception from
121 // the dtor, catch it here instead.
122 try
123 {
124 glUnmapBuffer( GL_ARRAY_BUFFER );
125 checkGlError( "unmapping vertices buffer", __FILE__, __LINE__ );
126 glBindBuffer( GL_ARRAY_BUFFER, 0 );
127 m_vertices = nullptr;
128 checkGlError( "unbinding vertices buffer", __FILE__, __LINE__ );
129 }
130 catch( const std::runtime_error& err )
131 {
132 wxLogError( wxT( "OpenGL did not shut down properly.\n\n%s" ), err.what() );
133 }
134
135 m_isMapped = false;
136}
137
138
139bool CACHED_CONTAINER_GPU::defragmentResize( unsigned int aNewSize )
140{
141 // A doubling resize transiently holds the old and the new buffer in video memory at the
142 // same time. On a large board this peak can exceed the driver's budget and trip a fatal
143 // out-of-memory abort (e.g. NVIDIA "Error code: 6"), which kills the process before any
144 // GL error can be observed.
145 const size_t oldBytes = static_cast<size_t>( m_currentSize ) * VERTEX_SIZE;
146 const size_t newBytes = static_cast<size_t>( aNewSize ) * VERTEX_SIZE;
147
149 0.15 ) )
150 {
152 throw KIGFX::GPU_OOM_ERROR( "Insufficient GPU memory to render this board; "
153 "switching to software rendering." );
154
156 return defragmentResizeStaged( aNewSize );
157
159 break;
160 }
161
162 if( !m_useCopyBuffer )
163 return defragmentResizeMemcpy( aNewSize );
164
165 wxCHECK( IsMapped(), false );
166
167 wxLogTrace( traceGalCachedContainerGpu,
168 wxT( "Resizing & defragmenting container from %d to %d" ), m_currentSize,
169 aNewSize );
170
171 // No shrinking if we cannot fit all the data
172 if( usedSpace() > aNewSize )
173 return false;
174
175#ifdef KICAD_GAL_PROFILE
176 PROF_TIMER totalTime;
177#endif /* KICAD_GAL_PROFILE */
178
179 GLuint newBuffer;
180
181 // glCopyBufferSubData requires a buffer to be unmapped
182 glUnmapBuffer( GL_ARRAY_BUFFER );
183
184 // Create a new destination buffer
185 glGenBuffers( 1, &newBuffer );
186
187 // It would be best to use GL_COPY_WRITE_BUFFER here,
188 // but it is not available everywhere
189#ifdef KICAD_GAL_PROFILE
190 GLint eaBuffer = -1;
191 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
192 wxASSERT( eaBuffer == 0 );
193#endif /* KICAD_GAL_PROFILE */
194 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
195 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
196 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
197
198 ITEMS::iterator it, it_end;
199 int newOffset = 0;
200
201 // Defragmentation
202 for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
203 {
204 VERTEX_ITEM* item = *it;
205 int itemOffset = item->GetOffset();
206 int itemSize = item->GetSize();
207
208 // Move an item to the new container
209 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, itemOffset * VERTEX_SIZE,
210 newOffset * VERTEX_SIZE, itemSize * VERTEX_SIZE );
211
212 // Update new offset
213 item->setOffset( newOffset );
214
215 // Move to the next free space
216 newOffset += itemSize;
217 }
218
219 // Move the current item and place it at the end
220 if( m_item->GetSize() > 0 )
221 {
222 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
223 m_item->GetOffset() * VERTEX_SIZE, newOffset * VERTEX_SIZE,
224 m_item->GetSize() * VERTEX_SIZE );
225
226 m_item->setOffset( newOffset );
227 m_chunkOffset = newOffset;
228 }
229
230 // Cleanup
231 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
232 glBindBuffer( GL_ARRAY_BUFFER, 0 );
233
234 // Previously we have unmapped the array buffer, now when it is also
235 // unbound, it may be officially marked as unmapped
236 m_isMapped = false;
237 glDeleteBuffers( 1, &m_glBufferHandle );
238
239 // Switch to the new vertex buffer
240 m_glBufferHandle = newBuffer;
241
242 try
243 {
244 Map();
245 }
246 catch( const std::runtime_error& )
247 {
248 // Map() failed, likely due to glMapBuffer returning null.
249 // The buffer is valid but we can't map it.
250 return false;
251 }
252
253 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
254
255#ifdef KICAD_GAL_PROFILE
256 totalTime.Stop();
257
258 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
259 m_currentSize - m_freeSpace, totalTime.msecs() );
260#endif /* KICAD_GAL_PROFILE */
261
262 m_freeSpace += ( aNewSize - m_currentSize );
263 m_currentSize = aNewSize;
264
265 wxLogTrace( traceGalProfile, "VBO size %d used %d", m_currentSize, AllItemsSize() );
266
267 // Now there is only one big chunk of free memory
268 m_freeChunks.clear();
269 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
270
271 return true;
272}
273
274
276{
277 wxCHECK( IsMapped(), false );
278
279 wxLogTrace( traceGalCachedContainerGpu,
280 wxT( "Resizing & defragmenting container (memcpy) from %d to %d" ), m_currentSize,
281 aNewSize );
282
283 // No shrinking if we cannot fit all the data
284 if( usedSpace() > aNewSize )
285 return false;
286
287#ifdef KICAD_GAL_PROFILE
288 PROF_TIMER totalTime;
289#endif /* KICAD_GAL_PROFILE */
290
291 GLuint newBuffer;
292 VERTEX* newBufferMem;
293
294 // Create the destination buffer
295 glGenBuffers( 1, &newBuffer );
296
297 // It would be best to use GL_COPY_WRITE_BUFFER here,
298 // but it is not available everywhere
299#ifdef KICAD_GAL_PROFILE
300 GLint eaBuffer = -1;
301 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
302 wxASSERT( eaBuffer == 0 );
303#endif /* KICAD_GAL_PROFILE */
304
305 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
306 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
307 newBufferMem = static_cast<VERTEX*>( glMapBuffer( GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY ) );
308 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
309
310 if( newBufferMem == nullptr )
311 {
312 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
313 glDeleteBuffers( 1, &newBuffer );
314 return false;
315 }
316
317 defragment( newBufferMem );
318
319 // Cleanup
320 glUnmapBuffer( GL_ELEMENT_ARRAY_BUFFER );
321 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
322 Unmap();
323 glDeleteBuffers( 1, &m_glBufferHandle );
324
325 // Switch to the new vertex buffer
326 m_glBufferHandle = newBuffer;
327
328 try
329 {
330 Map();
331 }
332 catch( const std::runtime_error& )
333 {
334 // Map() failed, likely due to glMapBuffer returning null.
335 // The buffer is valid but we can't map it.
336 return false;
337 }
338
339 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
340
341#ifdef KICAD_GAL_PROFILE
342 totalTime.Stop();
343
344 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
345 m_currentSize - m_freeSpace, totalTime.msecs() );
346#endif /* KICAD_GAL_PROFILE */
347
348 m_freeSpace += ( aNewSize - m_currentSize );
349 m_currentSize = aNewSize;
350
351 wxLogTrace( traceGalProfile, "VBO size %d used: %d", m_currentSize, AllItemsSize() );
352
353 // Now there is only one big chunk of free memory
354 m_freeChunks.clear();
355 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
356
357 return true;
358}
359
360
362{
363 wxCHECK( IsMapped(), false );
364
365 wxLogTrace( traceGalCachedContainerGpu,
366 wxT( "Resizing & defragmenting container (RAM staged) from %d to %d" ),
367 m_currentSize, aNewSize );
368
369 // No shrinking if we cannot fit all the data
370 if( usedSpace() > aNewSize )
371 return false;
372
373 const unsigned int usedVerts = usedSpace();
374
375 // Stage the compacted vertices in host memory so the old video buffer can be released
376 // before the larger replacement is allocated, keeping the peak VRAM at max(old, new).
377 std::unique_ptr<VERTEX[]> staging;
378
379 try
380 {
381 staging.reset( new VERTEX[usedVerts] );
382 }
383 catch( const std::bad_alloc& )
384 {
385 throw GPU_OOM_ERROR( "Out of memory while staging a GPU buffer resize; "
386 "switching to software rendering." );
387 }
388
389 // Reads from the mapped old buffer, so it must run before that buffer is released.
390 defragment( staging.get() );
391
392 Unmap();
393 glDeleteBuffers( 1, &m_glBufferHandle );
394
395 GLuint newBuffer;
396 glGenBuffers( 1, &newBuffer );
397 glBindBuffer( GL_ARRAY_BUFFER, newBuffer );
398 glBufferData( GL_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
399 glBindBuffer( GL_ARRAY_BUFFER, 0 );
400 checkGlError( "allocating staged buffer during defragmentation", __FILE__, __LINE__ );
401
402 m_glBufferHandle = newBuffer;
403
404 try
405 {
406 Map();
407 }
408 catch( const std::runtime_error& )
409 {
410 // Map() failed, likely due to glMapBuffer returning null.
411 return false;
412 }
413
414 if( usedVerts > 0 )
415 memcpy( m_vertices, staging.get(), usedVerts * VERTEX_SIZE );
416
417 checkGlError( "switching buffers during staged defragmentation", __FILE__, __LINE__ );
418
419 m_freeSpace += ( aNewSize - m_currentSize );
420 m_currentSize = aNewSize;
421
422 wxLogTrace( traceGalProfile, "VBO size %d used: %d", m_currentSize, AllItemsSize() );
423
424 // Now there is only one big chunk of free memory
425 m_freeChunks.clear();
426 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
427
428 return true;
429}
430
431
433{
434 unsigned int size = 0;
435
436 for( const auto& item : m_items )
437 {
438 size += item->GetSize();
439 }
440
441 return size;
442}
443
void Map() override
Finish the vertices updates stage.
bool m_isMapped
Vertex buffer handle.
virtual unsigned int AllItemsSize() const override
bool IsMapped() const override
Prepare the container for vertices updates.
bool defragmentResizeStaged(unsigned int aNewSize)
Grow the buffer while keeping the peak video memory at max(old, new) rather than old + new,...
bool defragmentResizeMemcpy(unsigned int aNewSize)
void Unmap() override
Finish the vertices updates stage.
CACHED_CONTAINER_GPU(unsigned int aSize=DEFAULT_SIZE)
bool defragmentResize(unsigned int aNewSize) override
Remove empty spaces between chunks and optionally resizes the container.
unsigned int m_glBufferHandle
Flag saying whether it is safe to use glCopyBufferSubData.
unsigned int m_chunkOffset
Maximal vertex index number stored in the container.
VERTEX_ITEM * m_item
Properties of currently modified chunk & item.
FREE_CHUNK_MAP m_freeChunks
Stored VERTEX_ITEMs.
void defragment(VERTEX *aTarget)
Transfer all stored data to a new buffer, removing empty spaces between the data chunks in the contai...
CACHED_CONTAINER(unsigned int aSize=DEFAULT_SIZE)
ITEMS m_items
Currently modified item.
Raised when a GPU buffer allocation is predicted to exceed the available video memory.
unsigned int m_currentSize
Store the initial size, so it can be resized to this on Clear()
unsigned int m_freeSpace
Current container size, expressed in vertices.
unsigned int usedSpace() const
Return size of the used memory space.
void setOffset(unsigned int aOffset)
Set data offset in the container.
Definition vertex_item.h:80
unsigned int GetOffset() const
Return data offset in the container.
Definition vertex_item.h:64
unsigned int GetSize() const
Return information about number of vertices stored.
Definition vertex_item.h:54
A small class to help profiling.
Definition profile.h:46
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
double msecs(bool aSinceLast=false)
Definition profile.h:147
static const wxChar *const traceGalCachedContainerGpu
Flag to enable debug output of the GAL OpenGL GPU cached container.
const wxChar *const traceGalProfile
Flag to enable debug output of GAL performance profiling.
The Cairo implementation of the graphics abstraction layer.
Definition eda_group.h:29
static constexpr size_t VERTEX_SIZE
@ REFUSE
Neither path fits; the caller should fall back to software rendering.
Definition utils.h:61
@ GPU_COPY
Fast GPU-side copy; the old and new buffers are briefly co-resident.
Definition utils.h:59
@ RAM_STAGE
Stage through host memory so only the larger of the two buffers is resident.
Definition utils.h:60
VRAM_RESIZE_STRATEGY chooseResizeStrategy(size_t aFreeVRAM, size_t aOldBytes, size_t aNewBytes, double aMarginFrac)
Decide how to grow a GPU vertex buffer given the free video memory budget.
Definition utils.cpp:234
size_t queryFreeVideoMemoryBytes()
Query the amount of free video memory the driver reports.
Definition utils.cpp:204
wxLogTrace helper definitions.
int checkGlError(const std::string &aInfo, const char *aFile, int aLine, bool aThrow)
Check if a recent OpenGL operation has failed.
Definition utils.cpp:44
Class to handle an item held in a container.