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
148 switch( KIGFX::chooseResizeStrategy( KIGFX::queryFreeVideoMemoryBytes(), oldBytes, newBytes, 0.15 ) )
149 {
151 throw KIGFX::GPU_OOM_ERROR( "Insufficient GPU memory to render this board; switching to software rendering." );
152
154 return defragmentResizeStaged( aNewSize );
155
157 break;
158 }
159
160 if( !m_useCopyBuffer )
161 return defragmentResizeMemcpy( aNewSize );
162
163 wxCHECK( IsMapped(), false );
164
165 wxLogTrace( traceGalCachedContainerGpu,
166 wxT( "Resizing & defragmenting container from %d to %d" ), m_currentSize,
167 aNewSize );
168
169 // No shrinking if we cannot fit all the data
170 if( usedSpace() > aNewSize )
171 return false;
172
173#ifdef KICAD_GAL_PROFILE
174 PROF_TIMER totalTime;
175#endif /* KICAD_GAL_PROFILE */
176
177 GLuint newBuffer;
178
179 // glCopyBufferSubData requires a buffer to be unmapped
180 glUnmapBuffer( GL_ARRAY_BUFFER );
181
182 // Create a new destination buffer
183 glGenBuffers( 1, &newBuffer );
184
185 // It would be best to use GL_COPY_WRITE_BUFFER here,
186 // but it is not available everywhere
187#ifdef KICAD_GAL_PROFILE
188 GLint eaBuffer = -1;
189 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
190 wxASSERT( eaBuffer == 0 );
191#endif /* KICAD_GAL_PROFILE */
192 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
193 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
194 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
195
196 std::set<VERTEX_ITEM*>::iterator it, it_end;
197 int newOffset = 0;
198
199 // Defragmentation
200 for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
201 {
202 VERTEX_ITEM* item = *it;
203 int itemOffset = item->GetOffset();
204 int itemSize = item->GetSize();
205
206 // Move an item to the new container
207 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, itemOffset * VERTEX_SIZE,
208 newOffset * VERTEX_SIZE, itemSize * VERTEX_SIZE );
209
210 // Update new offset
211 item->setOffset( newOffset );
212
213 // Move to the next free space
214 newOffset += itemSize;
215 }
216
217 // Move the current item and place it at the end
218 if( m_item->GetSize() > 0 )
219 {
220 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
221 m_item->GetOffset() * VERTEX_SIZE, newOffset * VERTEX_SIZE,
222 m_item->GetSize() * VERTEX_SIZE );
223
224 m_item->setOffset( newOffset );
225 m_chunkOffset = newOffset;
226 }
227
228 // Cleanup
229 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
230 glBindBuffer( GL_ARRAY_BUFFER, 0 );
231
232 // Previously we have unmapped the array buffer, now when it is also
233 // unbound, it may be officially marked as unmapped
234 m_isMapped = false;
235 glDeleteBuffers( 1, &m_glBufferHandle );
236
237 // Switch to the new vertex buffer
238 m_glBufferHandle = newBuffer;
239
240 try
241 {
242 Map();
243 }
244 catch( const std::runtime_error& )
245 {
246 // Map() failed, likely due to glMapBuffer returning null.
247 // The buffer is valid but we can't map it.
248 return false;
249 }
250
251 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
252
253#ifdef KICAD_GAL_PROFILE
254 totalTime.Stop();
255
256 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
257 m_currentSize - m_freeSpace, totalTime.msecs() );
258#endif /* KICAD_GAL_PROFILE */
259
260 m_freeSpace += ( aNewSize - m_currentSize );
261 m_currentSize = aNewSize;
262
263 wxLogTrace( traceGalProfile, "VBO size %d used %d", m_currentSize, AllItemsSize() );
264
265 // Now there is only one big chunk of free memory
266 m_freeChunks.clear();
267 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
268
269 return true;
270}
271
272
274{
275 wxCHECK( IsMapped(), false );
276
277 wxLogTrace( traceGalCachedContainerGpu,
278 wxT( "Resizing & defragmenting container (memcpy) from %d to %d" ), m_currentSize,
279 aNewSize );
280
281 // No shrinking if we cannot fit all the data
282 if( usedSpace() > aNewSize )
283 return false;
284
285#ifdef KICAD_GAL_PROFILE
286 PROF_TIMER totalTime;
287#endif /* KICAD_GAL_PROFILE */
288
289 GLuint newBuffer;
290 VERTEX* newBufferMem;
291
292 // Create the destination buffer
293 glGenBuffers( 1, &newBuffer );
294
295 // It would be best to use GL_COPY_WRITE_BUFFER here,
296 // but it is not available everywhere
297#ifdef KICAD_GAL_PROFILE
298 GLint eaBuffer = -1;
299 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
300 wxASSERT( eaBuffer == 0 );
301#endif /* KICAD_GAL_PROFILE */
302
303 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
304 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
305 newBufferMem = static_cast<VERTEX*>( glMapBuffer( GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY ) );
306 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
307
308 if( newBufferMem == nullptr )
309 {
310 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
311 glDeleteBuffers( 1, &newBuffer );
312 return false;
313 }
314
315 defragment( newBufferMem );
316
317 // Cleanup
318 glUnmapBuffer( GL_ELEMENT_ARRAY_BUFFER );
319 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
320 Unmap();
321 glDeleteBuffers( 1, &m_glBufferHandle );
322
323 // Switch to the new vertex buffer
324 m_glBufferHandle = newBuffer;
325
326 try
327 {
328 Map();
329 }
330 catch( const std::runtime_error& )
331 {
332 // Map() failed, likely due to glMapBuffer returning null.
333 // The buffer is valid but we can't map it.
334 return false;
335 }
336
337 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
338
339#ifdef KICAD_GAL_PROFILE
340 totalTime.Stop();
341
342 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
343 m_currentSize - m_freeSpace, totalTime.msecs() );
344#endif /* KICAD_GAL_PROFILE */
345
346 m_freeSpace += ( aNewSize - m_currentSize );
347 m_currentSize = aNewSize;
348
349 wxLogTrace( traceGalProfile, "VBO size %d used: %d", m_currentSize, AllItemsSize() );
350
351 // Now there is only one big chunk of free memory
352 m_freeChunks.clear();
353 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
354
355 return true;
356}
357
358
360{
361 wxCHECK( IsMapped(), false );
362
363 wxLogTrace( traceGalCachedContainerGpu,
364 wxT( "Resizing & defragmenting container (RAM staged) from %d to %d" ),
365 m_currentSize, aNewSize );
366
367 // No shrinking if we cannot fit all the data
368 if( usedSpace() > aNewSize )
369 return false;
370
371 const unsigned int usedVerts = usedSpace();
372
373 // Stage the compacted vertices in host memory so the old video buffer can be released
374 // before the larger replacement is allocated, keeping the peak VRAM at max(old, new).
375 std::unique_ptr<VERTEX[]> staging;
376
377 try
378 {
379 staging.reset( new VERTEX[usedVerts] );
380 }
381 catch( const std::bad_alloc& )
382 {
383 throw GPU_OOM_ERROR( "Out of memory while staging a GPU buffer resize; "
384 "switching to software rendering." );
385 }
386
387 // Reads from the mapped old buffer, so it must run before that buffer is released.
388 defragment( staging.get() );
389
390 Unmap();
391 glDeleteBuffers( 1, &m_glBufferHandle );
392
393 GLuint newBuffer;
394 glGenBuffers( 1, &newBuffer );
395 glBindBuffer( GL_ARRAY_BUFFER, newBuffer );
396 glBufferData( GL_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
397 glBindBuffer( GL_ARRAY_BUFFER, 0 );
398 checkGlError( "allocating staged buffer during defragmentation", __FILE__, __LINE__ );
399
400 m_glBufferHandle = newBuffer;
401
402 try
403 {
404 Map();
405 }
406 catch( const std::runtime_error& )
407 {
408 // Map() failed, likely due to glMapBuffer returning null.
409 return false;
410 }
411
412 if( usedVerts > 0 )
413 memcpy( m_vertices, staging.get(), usedVerts * VERTEX_SIZE );
414
415 checkGlError( "switching buffers during staged defragmentation", __FILE__, __LINE__ );
416
417 m_freeSpace += ( aNewSize - m_currentSize );
418 m_currentSize = aNewSize;
419
420 wxLogTrace( traceGalProfile, "VBO size %d used: %d", m_currentSize, AllItemsSize() );
421
422 // Now there is only one big chunk of free memory
423 m_freeChunks.clear();
424 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
425
426 return true;
427}
428
429
431{
432 unsigned int size = 0;
433
434 for( const auto& item : m_items )
435 {
436 size += item->GetSize();
437 }
438
439 return size;
440}
441
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.
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.
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: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.