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 // Name 0 is silently ignored by glDeleteBuffers, so the destructor becomes a no-op
84 m_isMapped = false;
86}
87
88
90{
91 if( m_isMapped )
92 Unmap();
93
94 if( glDeleteBuffers )
95 glDeleteBuffers( 1, &m_glBufferHandle );
96}
97
98
100{
101 wxCHECK( !IsMapped(), /*void*/ );
102
103 // OpenGL version might suddenly stop being available in Windows when an RDP session is started
104 if( !glBindBuffer )
105 throw std::runtime_error( "OpenGL no longer available!" );
106
107 glBindBuffer( GL_ARRAY_BUFFER, m_glBufferHandle );
108 m_vertices = static_cast<VERTEX*>( glMapBuffer( GL_ARRAY_BUFFER, GL_READ_WRITE ) );
109
110 if( checkGlError( "mapping vertices buffer", __FILE__, __LINE__ ) == GL_NO_ERROR
111 && m_vertices != nullptr )
112 {
113 m_isMapped = true;
114 }
115 else
116 {
117 m_vertices = nullptr;
118 glBindBuffer( GL_ARRAY_BUFFER, 0 );
119 throw std::runtime_error( "Could not map vertex buffer: glMapBuffer returned null" );
120 }
121}
122
123
125{
126 wxCHECK( IsMapped(), /*void*/ );
127
128 // This gets called from ~CACHED_CONTAINER_GPU. To avoid throwing an exception from
129 // the dtor, catch it here instead.
130 try
131 {
132 glUnmapBuffer( GL_ARRAY_BUFFER );
133 checkGlError( "unmapping vertices buffer", __FILE__, __LINE__ );
134 glBindBuffer( GL_ARRAY_BUFFER, 0 );
135 m_vertices = nullptr;
136 checkGlError( "unbinding vertices buffer", __FILE__, __LINE__ );
137 }
138 catch( const std::runtime_error& err )
139 {
140 wxLogError( wxT( "OpenGL did not shut down properly.\n\n%s" ), err.what() );
141 }
142
143 m_isMapped = false;
144}
145
146
147bool CACHED_CONTAINER_GPU::defragmentResize( unsigned int aNewSize )
148{
149 // A doubling resize transiently holds the old and the new buffer in video memory at the
150 // same time. On a large board this peak can exceed the driver's budget and trip a fatal
151 // out-of-memory abort (e.g. NVIDIA "Error code: 6"), which kills the process before any
152 // GL error can be observed.
153 const size_t oldBytes = static_cast<size_t>( m_currentSize ) * VERTEX_SIZE;
154 const size_t newBytes = static_cast<size_t>( aNewSize ) * VERTEX_SIZE;
155
156 switch( KIGFX::chooseResizeStrategy( KIGFX::queryFreeVideoMemoryBytes(), oldBytes, newBytes, 0.15 ) )
157 {
159 throw KIGFX::GPU_OOM_ERROR( "Insufficient GPU memory to render this board; switching to software rendering." );
160
162 return defragmentResizeStaged( aNewSize );
163
165 break;
166 }
167
168 if( !m_useCopyBuffer )
169 return defragmentResizeMemcpy( aNewSize );
170
171 wxCHECK( IsMapped(), false );
172
173 wxLogTrace( traceGalCachedContainerGpu,
174 wxT( "Resizing & defragmenting container from %d to %d" ), m_currentSize,
175 aNewSize );
176
177 // No shrinking if we cannot fit all the data
178 if( usedSpace() > aNewSize )
179 return false;
180
181#ifdef KICAD_GAL_PROFILE
182 PROF_TIMER totalTime;
183#endif /* KICAD_GAL_PROFILE */
184
185 GLuint newBuffer;
186
187 // glCopyBufferSubData requires a buffer to be unmapped
188 glUnmapBuffer( GL_ARRAY_BUFFER );
189
190 // Create a new destination buffer
191 glGenBuffers( 1, &newBuffer );
192
193 // It would be best to use GL_COPY_WRITE_BUFFER here,
194 // but it is not available everywhere
195#ifdef KICAD_GAL_PROFILE
196 GLint eaBuffer = -1;
197 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
198 wxASSERT( eaBuffer == 0 );
199#endif /* KICAD_GAL_PROFILE */
200 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
201 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
202 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
203
204 std::set<VERTEX_ITEM*>::iterator it, it_end;
205 int newOffset = 0;
206
207 // Defragmentation
208 for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
209 {
210 VERTEX_ITEM* item = *it;
211 int itemOffset = item->GetOffset();
212 int itemSize = item->GetSize();
213
214 // Move an item to the new container
215 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, itemOffset * VERTEX_SIZE,
216 newOffset * VERTEX_SIZE, itemSize * VERTEX_SIZE );
217
218 // Update new offset
219 item->setOffset( newOffset );
220
221 // Move to the next free space
222 newOffset += itemSize;
223 }
224
225 // Move the current item and place it at the end
226 if( m_item->GetSize() > 0 )
227 {
228 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
229 m_item->GetOffset() * VERTEX_SIZE, newOffset * VERTEX_SIZE,
230 m_item->GetSize() * VERTEX_SIZE );
231
232 m_item->setOffset( newOffset );
233 m_chunkOffset = newOffset;
234 }
235
236 // Cleanup
237 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
238 glBindBuffer( GL_ARRAY_BUFFER, 0 );
239
240 // Previously we have unmapped the array buffer, now when it is also
241 // unbound, it may be officially marked as unmapped
242 m_isMapped = false;
243 glDeleteBuffers( 1, &m_glBufferHandle );
244
245 // Switch to the new vertex buffer
246 m_glBufferHandle = newBuffer;
247
248 try
249 {
250 Map();
251 }
252 catch( const std::runtime_error& )
253 {
254 // Map() failed, likely due to glMapBuffer returning null.
255 // The buffer is valid but we can't map it.
256 return false;
257 }
258
259 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
260
261#ifdef KICAD_GAL_PROFILE
262 totalTime.Stop();
263
264 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
265 m_currentSize - m_freeSpace, totalTime.msecs() );
266#endif /* KICAD_GAL_PROFILE */
267
268 m_freeSpace += ( aNewSize - m_currentSize );
269 m_currentSize = aNewSize;
270
271 wxLogTrace( traceGalProfile, "VBO size %d used %d", m_currentSize, AllItemsSize() );
272
273 // Now there is only one big chunk of free memory
274 m_freeChunks.clear();
275 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
276
277 return true;
278}
279
280
282{
283 wxCHECK( IsMapped(), false );
284
285 wxLogTrace( traceGalCachedContainerGpu,
286 wxT( "Resizing & defragmenting container (memcpy) from %d to %d" ), m_currentSize,
287 aNewSize );
288
289 // No shrinking if we cannot fit all the data
290 if( usedSpace() > aNewSize )
291 return false;
292
293#ifdef KICAD_GAL_PROFILE
294 PROF_TIMER totalTime;
295#endif /* KICAD_GAL_PROFILE */
296
297 GLuint newBuffer;
298 VERTEX* newBufferMem;
299
300 // Create the destination buffer
301 glGenBuffers( 1, &newBuffer );
302
303 // It would be best to use GL_COPY_WRITE_BUFFER here,
304 // but it is not available everywhere
305#ifdef KICAD_GAL_PROFILE
306 GLint eaBuffer = -1;
307 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
308 wxASSERT( eaBuffer == 0 );
309#endif /* KICAD_GAL_PROFILE */
310
311 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
312 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
313 newBufferMem = static_cast<VERTEX*>( glMapBuffer( GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY ) );
314 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
315
316 if( newBufferMem == nullptr )
317 {
318 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
319 glDeleteBuffers( 1, &newBuffer );
320 return false;
321 }
322
323 defragment( newBufferMem );
324
325 // Cleanup
326 glUnmapBuffer( GL_ELEMENT_ARRAY_BUFFER );
327 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
328 Unmap();
329 glDeleteBuffers( 1, &m_glBufferHandle );
330
331 // Switch to the new vertex buffer
332 m_glBufferHandle = newBuffer;
333
334 try
335 {
336 Map();
337 }
338 catch( const std::runtime_error& )
339 {
340 // Map() failed, likely due to glMapBuffer returning null.
341 // The buffer is valid but we can't map it.
342 return false;
343 }
344
345 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
346
347#ifdef KICAD_GAL_PROFILE
348 totalTime.Stop();
349
350 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
351 m_currentSize - m_freeSpace, totalTime.msecs() );
352#endif /* KICAD_GAL_PROFILE */
353
354 m_freeSpace += ( aNewSize - m_currentSize );
355 m_currentSize = aNewSize;
356
357 wxLogTrace( traceGalProfile, "VBO size %d used: %d", m_currentSize, AllItemsSize() );
358
359 // Now there is only one big chunk of free memory
360 m_freeChunks.clear();
361 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
362
363 return true;
364}
365
366
368{
369 wxCHECK( IsMapped(), false );
370
371 wxLogTrace( traceGalCachedContainerGpu,
372 wxT( "Resizing & defragmenting container (RAM staged) from %d to %d" ),
373 m_currentSize, aNewSize );
374
375 // No shrinking if we cannot fit all the data
376 if( usedSpace() > aNewSize )
377 return false;
378
379 const unsigned int usedVerts = usedSpace();
380
381 // Stage the compacted vertices in host memory so the old video buffer can be released
382 // before the larger replacement is allocated, keeping the peak VRAM at max(old, new).
383 std::unique_ptr<VERTEX[]> staging;
384
385 try
386 {
387 staging.reset( new VERTEX[usedVerts] );
388 }
389 catch( const std::bad_alloc& )
390 {
391 throw GPU_OOM_ERROR( "Out of memory while staging a GPU buffer resize; "
392 "switching to software rendering." );
393 }
394
395 // Reads from the mapped old buffer, so it must run before that buffer is released.
396 defragment( staging.get() );
397
398 Unmap();
399 glDeleteBuffers( 1, &m_glBufferHandle );
400
401 GLuint newBuffer;
402 glGenBuffers( 1, &newBuffer );
403 glBindBuffer( GL_ARRAY_BUFFER, newBuffer );
404 glBufferData( GL_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
405 glBindBuffer( GL_ARRAY_BUFFER, 0 );
406 checkGlError( "allocating staged buffer during defragmentation", __FILE__, __LINE__ );
407
408 m_glBufferHandle = newBuffer;
409
410 try
411 {
412 Map();
413 }
414 catch( const std::runtime_error& )
415 {
416 // Map() failed, likely due to glMapBuffer returning null.
417 return false;
418 }
419
420 if( usedVerts > 0 )
421 memcpy( m_vertices, staging.get(), usedVerts * VERTEX_SIZE );
422
423 checkGlError( "switching buffers during staged defragmentation", __FILE__, __LINE__ );
424
425 m_freeSpace += ( aNewSize - m_currentSize );
426 m_currentSize = aNewSize;
427
428 wxLogTrace( traceGalProfile, "VBO size %d used: %d", m_currentSize, AllItemsSize() );
429
430 // Now there is only one big chunk of free memory
431 m_freeChunks.clear();
432 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
433
434 return true;
435}
436
437
439{
440 unsigned int size = 0;
441
442 for( const auto& item : m_items )
443 {
444 size += item->GetSize();
445 }
446
447 return size;
448}
449
void Map() override
Prepare the container for vertices updates.
bool m_isMapped
Flag saying if vertex buffer is currently mapped.
bool m_useCopyBuffer
Flag saying whether it is safe to use glCopyBufferSubData.
virtual unsigned int AllItemsSize() const override
void Abandon() override
Give up ownership of any GL objects without deleting them.
bool IsMapped() const override
Return true if vertex buffer is currently mapped.
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
Vertex buffer handle.
std::set< VERTEX_ITEM * > m_items
Stored VERTEX_ITEMs.
VERTEX_ITEM * m_item
Currently modified item.
FREE_CHUNK_MAP m_freeChunks
Store size & offset of free chunks.
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)
Raised when a GPU buffer allocation is predicted to exceed the available video memory.
VERTEX * m_vertices
Actual storage memory.
unsigned int m_currentSize
Current container size, expressed in vertices.
unsigned int m_freeSpace
Free space left in the container, 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:148
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:30
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.