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 
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 #include <profile.h>
38 #include <trace_helpers.h>
39 
40 using namespace KIGFX;
41 
49 static const wxChar* const traceGalCachedContainerGpu = wxT( "KICAD_GAL_CACHED_CONTAINER_GPU" );
50 
51 
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 
129 bool 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,
193  m_item->GetSize() * 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 
233 bool CACHED_CONTAINER_GPU::defragmentResizeMemcpy( unsigned int aNewSize )
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 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:236
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...
const wxChar *const traceGalProfile
Flag to enable debug output of GAL performance profiling.
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.
virtual unsigned int AllItemsSize() const override
void setOffset(unsigned int aOffset)
Set data offset in the container.
Definition: vertex_item.h:84
wxLogTrace helper definitions.
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
#define KI_TRACE(...)
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.