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 <maciej.suminski@cern.ch>
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 
29 #include <gal/opengl/vertex_item.h>
30 #include <gal/opengl/shader.h>
31 #include <gal/opengl/utils.h>
32 
33 #include <wx/log.h>
34 
35 #include <list>
36 
37 #ifdef KICAD_GAL_PROFILE
38 #include <profile.h>
39 #endif /* KICAD_GAL_PROFILE */
40 
41 using namespace KIGFX;
42 
50 static const wxChar* const traceGalCachedContainerGpu = wxT( "KICAD_GAL_CACHED_CONTAINER_GPU" );
51 
52 
54  CACHED_CONTAINER( aSize ),
55  m_isMapped( false ),
56  m_glBufferHandle( -1 )
57 {
58  m_useCopyBuffer = GLEW_ARB_copy_buffer;
59 
60  wxString vendor( glGetString( GL_VENDOR ) );
61 
62  // workaround for intel GPU drivers: disable glCopyBuffer, causes crashes/freezes on
63  // certain driver versions
64  if( vendor.Contains( "Intel " ) || vendor.Contains( "etnaviv" ) )
65  {
66  m_useCopyBuffer = false;
67  }
68 
69 
70  glGenBuffers( 1, &m_glBufferHandle );
71  glBindBuffer( GL_ARRAY_BUFFER, m_glBufferHandle );
72  glBufferData( GL_ARRAY_BUFFER, m_currentSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
73  glBindBuffer( GL_ARRAY_BUFFER, 0 );
74  checkGlError( "allocating video memory for cached container", __FILE__, __LINE__ );
75 }
76 
77 
79 {
80  if( m_isMapped )
81  Unmap();
82 
83  if( glDeleteBuffers )
84  glDeleteBuffers( 1, &m_glBufferHandle );
85 }
86 
87 
89 {
90  wxCHECK( !IsMapped(), /*void*/ );
91 
92  // OpenGL version might suddenly stop being available in Windows when an RDP session is started
93  if( !glBindBuffer )
94  throw std::runtime_error( "OpenGL no longer available!" );
95 
96  glBindBuffer( GL_ARRAY_BUFFER, m_glBufferHandle );
97  m_vertices = static_cast<VERTEX*>( glMapBuffer( GL_ARRAY_BUFFER, GL_READ_WRITE ) );
98 
99  if( checkGlError( "mapping vertices buffer", __FILE__, __LINE__ ) == GL_NO_ERROR )
100  m_isMapped = true;
101 }
102 
103 
105 {
106  wxCHECK( IsMapped(), /*void*/ );
107 
108  // This gets called from ~CACHED_CONTAINER_GPU. To avoid throwing an exception from
109  // the dtor, catch it here instead.
110  try
111  {
112  glUnmapBuffer( GL_ARRAY_BUFFER );
113  checkGlError( "unmapping vertices buffer", __FILE__, __LINE__ );
114  glBindBuffer( GL_ARRAY_BUFFER, 0 );
115  m_vertices = nullptr;
116  checkGlError( "unbinding vertices buffer", __FILE__, __LINE__ );
117  }
118  catch( const std::runtime_error& err )
119  {
120  wxLogError( wxT( "OpenGL did not shut down properly.\n\n%s" ), err.what() );
121  }
122 
123  m_isMapped = false;
124 }
125 
126 
127 bool CACHED_CONTAINER_GPU::defragmentResize( unsigned int aNewSize )
128 {
129  if( !m_useCopyBuffer )
130  return defragmentResizeMemcpy( aNewSize );
131 
132  wxCHECK( IsMapped(), false );
133 
134  wxLogTrace( traceGalCachedContainerGpu,
135  wxT( "Resizing & defragmenting container from %d to %d" ), m_currentSize,
136  aNewSize );
137 
138  // No shrinking if we cannot fit all the data
139  if( usedSpace() > aNewSize )
140  return false;
141 
142 #ifdef KICAD_GAL_PROFILE
143  PROF_COUNTER totalTime;
144 #endif /* KICAD_GAL_PROFILE */
145 
146  GLuint newBuffer;
147 
148  // glCopyBufferSubData requires a buffer to be unmapped
149  glUnmapBuffer( GL_ARRAY_BUFFER );
150 
151  // Create a new destination buffer
152  glGenBuffers( 1, &newBuffer );
153 
154  // It would be best to use GL_COPY_WRITE_BUFFER here,
155  // but it is not available everywhere
156 #ifdef KICAD_GAL_PROFILE
157  GLint eaBuffer = -1;
158  glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
159  wxASSERT( eaBuffer == 0 );
160 #endif /* KICAD_GAL_PROFILE */
161  glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
162  glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
163  checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
164 
165  ITEMS::iterator it, it_end;
166  int newOffset = 0;
167 
168  // Defragmentation
169  for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it )
170  {
171  VERTEX_ITEM* item = *it;
172  int itemOffset = item->GetOffset();
173  int itemSize = item->GetSize();
174 
175  // Move an item to the new container
176  glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, itemOffset * VERTEX_SIZE,
177  newOffset * VERTEX_SIZE, itemSize * VERTEX_SIZE );
178 
179  // Update new offset
180  item->setOffset( newOffset );
181 
182  // Move to the next free space
183  newOffset += itemSize;
184  }
185 
186  // Move the current item and place it at the end
187  if( m_item->GetSize() > 0 )
188  {
189  glCopyBufferSubData( GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER,
190  m_item->GetOffset() * VERTEX_SIZE, newOffset * VERTEX_SIZE,
191  m_item->GetSize() * VERTEX_SIZE );
192 
193  m_item->setOffset( newOffset );
194  m_chunkOffset = newOffset;
195  }
196 
197  // Cleanup
198  glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
199  glBindBuffer( GL_ARRAY_BUFFER, 0 );
200 
201  // Previously we have unmapped the array buffer, now when it is also
202  // unbound, it may be officially marked as unmapped
203  m_isMapped = false;
204  glDeleteBuffers( 1, &m_glBufferHandle );
205 
206  // Switch to the new vertex buffer
207  m_glBufferHandle = newBuffer;
208  Map();
209  checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
210 
211 #ifdef KICAD_GAL_PROFILE
212  totalTime.Stop();
213 
214  wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
215  m_currentSize - m_freeSpace, totalTime.msecs() );
216 #endif /* KICAD_GAL_PROFILE */
217 
218  m_freeSpace += ( aNewSize - m_currentSize );
219  m_currentSize = aNewSize;
220 
221  // Now there is only one big chunk of free memory
222  m_freeChunks.clear();
223  m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
224 
225  return true;
226 }
227 
228 
229 bool CACHED_CONTAINER_GPU::defragmentResizeMemcpy( unsigned int aNewSize )
230 {
231  wxCHECK( IsMapped(), false );
232 
233  wxLogTrace( traceGalCachedContainerGpu,
234  wxT( "Resizing & defragmenting container (memcpy) from %d to %d" ), m_currentSize,
235  aNewSize );
236 
237  // No shrinking if we cannot fit all the data
238  if( usedSpace() > aNewSize )
239  return false;
240 
241 #ifdef KICAD_GAL_PROFILE
242  PROF_COUNTER totalTime;
243 #endif /* KICAD_GAL_PROFILE */
244 
245  GLuint newBuffer;
246  VERTEX* newBufferMem;
247 
248  // Create the destination buffer
249  glGenBuffers( 1, &newBuffer );
250 
251  // It would be best to use GL_COPY_WRITE_BUFFER here,
252  // but it is not available everywhere
253 #ifdef KICAD_GAL_PROFILE
254  GLint eaBuffer = -1;
255  glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &eaBuffer );
256  wxASSERT( eaBuffer == 0 );
257 #endif /* KICAD_GAL_PROFILE */
258  glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, newBuffer );
259  glBufferData( GL_ELEMENT_ARRAY_BUFFER, aNewSize * VERTEX_SIZE, nullptr, GL_DYNAMIC_DRAW );
260  newBufferMem = static_cast<VERTEX*>( glMapBuffer( GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY ) );
261  checkGlError( "creating buffer during defragmentation", __FILE__, __LINE__ );
262 
263  defragment( newBufferMem );
264 
265  // Cleanup
266  glUnmapBuffer( GL_ELEMENT_ARRAY_BUFFER );
267  glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
268  Unmap();
269  glDeleteBuffers( 1, &m_glBufferHandle );
270 
271  // Switch to the new vertex buffer
272  m_glBufferHandle = newBuffer;
273  Map();
274  checkGlError( "switching buffers during defragmentation", __FILE__, __LINE__ );
275 
276 #ifdef KICAD_GAL_PROFILE
277  totalTime.Stop();
278 
279  wxLogTrace( traceGalCachedContainerGpu, "Defragmented container storing %d vertices / %.1f ms",
280  m_currentSize - m_freeSpace, totalTime.msecs() );
281 #endif /* KICAD_GAL_PROFILE */
282 
283  m_freeSpace += ( aNewSize - m_currentSize );
284  m_currentSize = aNewSize;
285 
286  // Now there is only one big chunk of free memory
287  m_freeChunks.clear();
288  m_freeChunks.insert( std::make_pair( m_freeSpace, m_currentSize - m_freeSpace ) );
289 
290  return true;
291 }
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition: profile.h:85
The Cairo implementation of the graphics abstraction layer.
Definition: color4d.cpp:191
double msecs(bool aSinceLast=false)
Definition: profile.h:146
unsigned int GetSize() const
Return information about number of vertices stored.
Definition: vertex_item.h:58
void defragment(VERTEX *aTarget)
Transfer all stored data to a new buffer, removing empty spaces between the data chunks in the contai...
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
unsigned int m_glBufferHandle
Flag saying whether it is safe to use glCopyBufferSubData.
unsigned int m_currentSize
Store the initial size, so it can be resized to this on Clear()
CACHED_CONTAINER_GPU(unsigned int aSize=DEFAULT_SIZE)
bool m_isMapped
Vertex buffer handle.
A small class to help profiling.
Definition: profile.h:45
bool IsMapped() const override
Prepare the container for vertices updates.
void Unmap() override
Finish the vertices updates stage.
ITEMS m_items
Currently modified item.
VERTEX_ITEM * m_item
Properties of currently modified chunk & item.
unsigned int m_freeSpace
Current container size, expressed in vertices.
Class to store VERTEX instances with caching.
void setOffset(unsigned int aOffset)
Set data offset in the container.
Definition: vertex_item.h:84
void Map() override
Finish the vertices updates stage.
bool defragmentResizeMemcpy(unsigned int aNewSize)
Flag saying if vertex buffer is currently mapped.
FREE_CHUNK_MAP m_freeChunks
Stored VERTEX_ITEMs.
unsigned int usedSpace() const
Return size of the used memory space.
Class to handle an item held in a container.
bool defragmentResize(unsigned int aNewSize) override
Remove empty spaces between chunks and optionally resizes the container.
static constexpr size_t VERTEX_SIZE
Definition: vertex_common.h:67
unsigned int GetOffset() const
Return data offset in the container.
Definition: vertex_item.h:68
unsigned int m_chunkOffset
Maximal vertex index number stored in the container.
static const wxChar *const traceGalCachedContainerGpu
Flag to enable debug output of the GAL OpenGL GPU cached container.