KiCad PCB EDA Suite
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 (C) 2020-2021 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, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
30#include <gal/opengl/shader.h>
31#include <gal/opengl/utils.h>
32
33#include <wx/log.h>
34
35#include <list>
36
37#include <profile.h>
38#include <trace_helpers.h>
39
40using namespace KIGFX;
41
49static const wxChar* const traceGalCachedContainerGpu = wxT( "KICAD_GAL_CACHED_CONTAINER_GPU" );
50
51
52CACHED_CONTAINER_GPU::CACHED_CONTAINER_GPU( unsigned int aSize ) :
53 CACHED_CONTAINER( aSize ),
54 m_isMapped( false ),
55 m_glBufferHandle( -1 )
56{
57 m_useCopyBuffer = GLEW_ARB_copy_buffer;
58
59 wxString vendor( glGetString( GL_VENDOR ) );
60
61 // workaround for intel GPU drivers:
62 // disable glCopyBuffer, causes crashes/freezes on certain driver versions
63 // Note, Intel's GL_VENDOR string varies depending on GPU/driver generation
64 // But generally always starts with Intel at least
65 if( vendor.StartsWith( "Intel" ) || vendor.Contains( "etnaviv" ) )
66 {
67 m_useCopyBuffer = false;
68 }
69
70 KI_TRACE( traceGalProfile, "VBO initial size: %d\n", m_currentSize );
71
72 glGenBuffers( 1, &m_glBufferHandle );
73 glBindBuffer( GL_ARRAY_BUFFER, m_glBufferHandle );
74 glBufferData( GL_ARRAY_BUFFER, m_currentSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
75 glBindBuffer( GL_ARRAY_BUFFER, 0 );
76 checkGlError( "allocating video memory for cached container", __FILE__, __LINE__ );
77}
78
79
81{
82 if( m_isMapped )
83 Unmap();
84
85 if( glDeleteBuffers )
86 glDeleteBuffers( 1, &m_glBufferHandle );
87}
88
89
91{
92 wxCHECK( !IsMapped(), /*void*/ );
93
94 // OpenGL version might suddenly stop being available in Windows when an RDP session is started
95 if( !glBindBuffer )
96 throw std::runtime_error( "OpenGL no longer available!" );
97
98 glBindBuffer( GL_ARRAY_BUFFER, m_glBufferHandle );
99 m_vertices = static_cast<VERTEX*>( glMapBuffer( GL_ARRAY_BUFFER, GL_READ_WRITE ) );
100
101 if( checkGlError( "mapping vertices buffer", __FILE__, __LINE__ ) == GL_NO_ERROR )
102 m_isMapped = true;
103}
104
105
107{
108 wxCHECK( IsMapped(), /*void*/ );
109
110 // This gets called from ~CACHED_CONTAINER_GPU. To avoid throwing an exception from
111 // the dtor, catch it here instead.
112 try
113 {
114 glUnmapBuffer( GL_ARRAY_BUFFER );
115 checkGlError( "unmapping vertices buffer", __FILE__, __LINE__ );
116 glBindBuffer( GL_ARRAY_BUFFER, 0 );
117 m_vertices = nullptr;
118 checkGlError( "unbinding vertices buffer", __FILE__, __LINE__ );
119 }
120 catch( const std::runtime_error& err )
121 {
122 wxLogError( wxT( "OpenGL did not shut down properly.\n\n%s" ), err.what() );
123 }
124
125 m_isMapped = false;
126}
127
128
129bool CACHED_CONTAINER_GPU::defragmentResize( unsigned int aNewSize )
130{
131 if( !m_useCopyBuffer )
132 return defragmentResizeMemcpy( aNewSize );
133
134 wxCHECK( IsMapped(), false );
135
136 wxLogTrace( traceGalCachedContainerGpu,
137 wxT( "Resizing & defragmenting container from %d to %d" ), m_currentSize,
138 aNewSize );
139
140 // No shrinking if we cannot fit all the data
141 if( usedSpace() > aNewSize )
142 return false;
143
144#ifdef KICAD_GAL_PROFILE
145 PROF_COUNTER totalTime;
146#endif /* KICAD_GAL_PROFILE */
147
148 GLuint newBuffer;
149
150 // glCopyBufferSubData requires a buffer to be unmapped
151 glUnmapBuffer( GL_ARRAY_BUFFER );
152
153 // Create a new destination buffer
154 glGenBuffers( 1, &newBuffer );
155
156 // It would be best to use GL_COPY_WRITE_BUFFER here,
157 // but it is not available everywhere
158#ifdef KICAD_GAL_PROFILE
159 GLint eaBuffer = -1;
160 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
161 wxASSERT( eaBuffer == 0 );
162#endif /* KICAD_GAL_PROFILE */
163 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
164 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
165 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
166
167 ITEMS::iterator it, it_end;
168 int newOffset = 0;
169
170 // Defragmentation
171 for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
172 {
173 VERTEX_ITEM* item = *it;
174 int itemOffset = item->GetOffset();
175 int itemSize = item->GetSize();
176
177 // Move an item to the new container
178 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, itemOffset * VERTEX_SIZE,
179 newOffset * VERTEX_SIZE, itemSize * VERTEX_SIZE );
180
181 // Update new offset
182 item->setOffset( newOffset );
183
184 // Move to the next free space
185 newOffset += itemSize;
186 }
187
188 // Move the current item and place it at the end
189 if( m_item->GetSize() > 0 )
190 {
191 glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
192 m_item->GetOffset() * VERTEX_SIZE, newOffset * VERTEX_SIZE,
194
195 m_item->setOffset( newOffset );
196 m_chunkOffset = newOffset;
197 }
198
199 // Cleanup
200 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
201 glBindBuffer( GL_ARRAY_BUFFER, 0 );
202
203 // Previously we have unmapped the array buffer, now when it is also
204 // unbound, it may be officially marked as unmapped
205 m_isMapped = false;
206 glDeleteBuffers( 1, &m_glBufferHandle );
207
208 // Switch to the new vertex buffer
209 m_glBufferHandle = newBuffer;
210 Map();
211 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
212
213#ifdef KICAD_GAL_PROFILE
214 totalTime.Stop();
215
216 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
217 m_currentSize - m_freeSpace, totalTime.msecs() );
218#endif /* KICAD_GAL_PROFILE */
219
220 m_freeSpace += ( aNewSize - m_currentSize );
221 m_currentSize = aNewSize;
222
223 KI_TRACE( traceGalProfile, "VBO size %d used %d\n", m_currentSize, AllItemsSize() );
224
225 // Now there is only one big chunk of free memory
226 m_freeChunks.clear();
227 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
228
229 return true;
230}
231
232
234{
235 wxCHECK( IsMapped(), false );
236
237 wxLogTrace( traceGalCachedContainerGpu,
238 wxT( "Resizing & defragmenting container (memcpy) from %d to %d" ), m_currentSize,
239 aNewSize );
240
241 // No shrinking if we cannot fit all the data
242 if( usedSpace() > aNewSize )
243 return false;
244
245#ifdef KICAD_GAL_PROFILE
246 PROF_COUNTER totalTime;
247#endif /* KICAD_GAL_PROFILE */
248
249 GLuint newBuffer;
250 VERTEX* newBufferMem;
251
252 // Create the destination buffer
253 glGenBuffers( 1, &newBuffer );
254
255 // It would be best to use GL_COPY_WRITE_BUFFER here,
256 // but it is not available everywhere
257#ifdef KICAD_GAL_PROFILE
258 GLint eaBuffer = -1;
259 glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
260 wxASSERT( eaBuffer == 0 );
261#endif /* KICAD_GAL_PROFILE */
262 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
263 glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
264 newBufferMem = static_cast<VERTEX*>( glMapBuffer( GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY ) );
265 checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
266
267 defragment( newBufferMem );
268
269 // Cleanup
270 glUnmapBuffer( GL_ELEMENT_ARRAY_BUFFER );
271 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
272 Unmap();
273 glDeleteBuffers( 1, &m_glBufferHandle );
274
275 // Switch to the new vertex buffer
276 m_glBufferHandle = newBuffer;
277 Map();
278 checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
279
280#ifdef KICAD_GAL_PROFILE
281 totalTime.Stop();
282
283 wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
284 m_currentSize - m_freeSpace, totalTime.msecs() );
285#endif /* KICAD_GAL_PROFILE */
286
287 m_freeSpace += ( aNewSize - m_currentSize );
288 m_currentSize = aNewSize;
289
290 KI_TRACE( traceGalProfile, "VBO size %d used: %d \n", m_currentSize, AllItemsSize() );
291
292 // Now there is only one big chunk of free memory
293 m_freeChunks.clear();
294 m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
295
296 return true;
297}
298
299
301{
302 unsigned int size = 0;
303
304 for( const auto& item : m_items )
305 {
306 size += item->GetSize();
307 }
308
309 return size;
310}
311
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 defragmentResizeMemcpy(unsigned int aNewSize)
Flag saying if vertex buffer is currently mapped.
void Unmap() override
Finish the vertices updates stage.
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.
Class to store VERTEX instances with caching.
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...
ITEMS m_items
Currently modified item.
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:84
unsigned int GetOffset() const
Return data offset in the container.
Definition: vertex_item.h:68
unsigned int GetSize() const
Return information about number of vertices stored.
Definition: vertex_item.h:58
A thread-safe event counter.
Definition: profile.h:226
const wxChar *const traceGalProfile
Flag to enable debug output of GAL performance profiling.
static const wxChar *const traceGalCachedContainerGpu
Flag to enable debug output of the GAL OpenGL GPU cached container.
The Cairo implementation of the graphics abstraction layer.
Definition: color4d.cpp:243
static constexpr size_t VERTEX_SIZE
Definition: vertex_common.h:67
wxLogTrace helper definitions.
#define KI_TRACE(aWhat,...)
int checkGlError(const std::string &aInfo, const char *aFile, int aLine, bool aThrow)
Check if a recent OpenGL operation has failed.
Definition: utils.cpp:45
Class to handle an item held in a container.