KiCad PCB EDA Suite
Loading...
Searching...
No Matches
cached_container.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, 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
33
37#include <gal/opengl/utils.h>
38
39#include <list>
40#include <algorithm>
41#include <cassert>
42
43#ifdef __WIN32__
44#include <windows.h>
45#endif
46
47#ifdef KICAD_GAL_PROFILE
48#include <wx/log.h>
49#include <core/profile.h>
50#endif /* KICAD_GAL_PROFILE */
51
52using namespace KIGFX;
53
55 VERTEX_CONTAINER( aSize ),
56 m_item( nullptr ),
57 m_chunkSize( 0 ),
58 m_chunkOffset( 0 ),
59 m_maxIndex( 0 )
60{
61 // In the beginning there is only free space
62 m_freeChunks.insert( std::make_pair( aSize, 0 ) );
63}
64
65
67{
68 assert( aItem != nullptr );
69
70 unsigned int itemSize = aItem->GetSize();
71 m_item = aItem;
72 m_chunkSize = itemSize;
73
74 // Get the previously set offset if the item was stored previously
75 m_chunkOffset = itemSize > 0 ? aItem->GetOffset() : -1;
76}
77
78
80{
81 assert( m_item != nullptr );
82
83 unsigned int itemSize = m_item->GetSize();
84
85 // Finishing the previously edited item
86 if( itemSize < m_chunkSize )
87 {
88 // There is some not used but reserved memory left, so we should return it to the pool
89 int itemOffset = m_item->GetOffset();
90
91 // Add the not used memory back to the pool
92 addFreeChunk( itemOffset + itemSize, m_chunkSize - itemSize );
93
94 // mergeFreeChunks(); // veery slow and buggy
95
96 m_maxIndex = std::max( itemOffset + itemSize, m_maxIndex );
97 }
98
99 if( itemSize > 0 )
100 m_items.insert( m_item );
101
102 m_item = nullptr;
103 m_chunkSize = 0;
104 m_chunkOffset = 0;
105
106#if CACHED_CONTAINER_TEST > 1
107 test();
108#endif
109}
110
111
112VERTEX* CACHED_CONTAINER::Allocate( unsigned int aSize )
113{
114 assert( m_item != nullptr );
115 assert( IsMapped() );
116
117 if( m_failed )
118 return nullptr;
119
120 unsigned int itemSize = m_item->GetSize();
121 unsigned int newSize = itemSize + aSize;
122
123 if( newSize > m_chunkSize )
124 {
125 // There is not enough space in the currently reserved chunk, so we have to resize it
126 if( !reallocate( newSize ) )
127 {
128 m_failed = true;
129 return nullptr;
130 }
131 }
132
133 // Guard against a null vertex buffer (can occur if the GPU mapping was silently
134 // invalidated, e.g. on macOS under memory pressure).
135 if( !m_vertices )
136 {
137 m_failed = true;
138 return nullptr;
139 }
140
141 VERTEX* reserved = &m_vertices[m_chunkOffset + itemSize];
142
143 // Now the item officially possesses the memory chunk
144 m_item->setSize( newSize );
145
146 // The content has to be updated
147 m_dirty = true;
148
149#if CACHED_CONTAINER_TEST > 0
150 test();
151#endif
152#if CACHED_CONTAINER_TEST > 2
155#endif
156
157 return reserved;
158}
159
160
162{
163 assert( aItem != nullptr );
164 assert( m_items.find( aItem ) != m_items.end() || aItem->GetSize() == 0 );
165
166 int size = aItem->GetSize();
167
168 if( size == 0 )
169 return; // Item is not stored here
170
171 int offset = aItem->GetOffset();
172
173 // Insert a free memory chunk entry in the place where item was stored
174 addFreeChunk( offset, size );
175
176 // Indicate that the item is not stored in the container anymore
177 aItem->setSize( 0 );
178
179 m_items.erase( aItem );
180
181#if CACHED_CONTAINER_TEST > 0
182 test();
183#endif
184
185 // This dynamic memory freeing optimize memory usage, but in fact can create
186 // out of memory issues because freeing and reallocation large chunks of memory
187 // can create memory fragmentation and no room to reallocate large chunks
188 // after many free/reallocate cycles during a session using the same complex board
189 // So it can be disable.
190 // Currently: it is disable to avoid "out of memory" issues
191#if 0
192 // Dynamic memory freeing, there is no point in holding
193 // a large amount of memory when there is no use for it
195 {
197 }
198#endif
199}
200
201
203{
205 m_maxIndex = 0;
206 m_failed = false;
207
208 // Set the size of all the stored VERTEX_ITEMs to 0, so it is clear that they are not held
209 // in the container anymore
210 for( ITEMS::iterator it = m_items.begin(); it != m_items.end(); ++it )
211 ( *it )->setSize( 0 );
212
213 m_items.clear();
214
215 // Now there is only free space left
216 m_freeChunks.clear();
217 m_freeChunks.insert( std::make_pair( m_freeSpace, 0 ) );
218}
219
220
221bool CACHED_CONTAINER::reallocate( unsigned int aSize )
222{
223 assert( aSize > 0 );
224 assert( IsMapped() );
225
226 unsigned int itemSize = m_item->GetSize();
227
228 // Find a free space chunk >= aSize
229 FREE_CHUNK_MAP::iterator newChunk = m_freeChunks.lower_bound( aSize );
230
231 // Is there enough space to store vertices?
232 if( newChunk == m_freeChunks.end() )
233 {
234 bool result;
235
236 // Would it be enough to double the current space?
237 if( aSize < m_freeSpace + m_currentSize )
238 {
239 // Yes: exponential growing
241 }
242 else
243 {
244 // No: grow to the nearest greater power of 2
245 result = defragmentResize( pow( 2, ceil( log2( m_currentSize * 2 + aSize ) ) ) );
246 }
247
248 if( !result )
249 return false;
250
251 newChunk = m_freeChunks.lower_bound( aSize );
252 assert( newChunk != m_freeChunks.end() );
253 }
254
255 // Parameters of the allocated chunk
256 unsigned int newChunkSize = getChunkSize( *newChunk );
257 unsigned int newChunkOffset = getChunkOffset( *newChunk );
258
259 assert( newChunkSize >= aSize );
260 assert( newChunkOffset < m_currentSize );
261
262 // Check if the item was previously stored in the container
263 if( itemSize > 0 )
264 {
265 // Safety check: m_vertices must be valid at this point. On some platforms (notably
266 // macOS with Metal-backed OpenGL), the GPU buffer mapping can silently fail or be
267 // invalidated under memory pressure, leaving m_vertices null even after a successful
268 // defragmentResize(). Bail out rather than crash on the memcpy.
269 if( !m_vertices )
270 return false;
271
272 // The item was reallocated, so we have to copy all the old data to the new place
273 memcpy( &m_vertices[newChunkOffset], &m_vertices[m_chunkOffset], itemSize * VERTEX_SIZE );
274
275 // Free the space used by the previous chunk
277 }
278
279 // Remove the new allocated chunk from the free space pool
280 m_freeChunks.erase( newChunk );
281 m_freeSpace -= newChunkSize;
282
283 m_chunkSize = newChunkSize;
284 m_chunkOffset = newChunkOffset;
285
286 m_item->setOffset( m_chunkOffset );
287
288 return true;
289}
290
291
293{
294 // Defragmentation
295 ITEMS::iterator it, it_end;
296 int newOffset = 0;
297
298 [&]()
299 {
300#ifdef __WIN32__
301 #ifdef __MINGW32__
302 // currently, because SEH (Structured Exception Handling) is not documented on msys
303 // (for instance __try or __try1 exists without doc) or is not supported, do nothing
304 #else
305 __try
306 #endif
307#endif
308 {
309 for( VERTEX_ITEM* item : m_items )
310 {
311 int itemOffset = item->GetOffset();
312 int itemSize = item->GetSize();
313
314 // Move an item to the new container
315 memcpy( &aTarget[newOffset], &m_vertices[itemOffset], itemSize * VERTEX_SIZE );
316
317 // Update new offset
318 item->setOffset( newOffset );
319
320 // Move to the next free space
321 newOffset += itemSize;
322 }
323
324 // Move the current item and place it at the end
325 if( m_item->GetSize() > 0 )
326 {
327 memcpy( &aTarget[newOffset], &m_vertices[m_item->GetOffset()],
328 m_item->GetSize() * VERTEX_SIZE );
329 m_item->setOffset( newOffset );
330 m_chunkOffset = newOffset;
331 }
332 }
333#ifdef __WIN32__
334 #ifdef __MINGW32__
335 // currently, because SEH (Structured Exception Handling) is not documented on msys
336 // (for instance __except1 exists without doc) or is not supported, do nothing
337 #else
338 __except( GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER
339 : EXCEPTION_CONTINUE_SEARCH )
340 {
341 throw std::runtime_error(
342 "Access violation in defragment. This is usually an indicator of "
343 "system or GPU memory running low." );
344 };
345 #endif
346#endif
347 }();
348
350}
351
352
354{
355 if( m_freeChunks.size() <= 1 ) // There are no chunks that can be merged
356 return;
357
358#ifdef KICAD_GAL_PROFILE
359 PROF_TIMER totalTime;
360#endif /* KICAD_GAL_PROFILE */
361
362 // Reversed free chunks map - this one stores chunk size with its offset as the key
363 std::list<CHUNK> freeChunks;
364
365 FREE_CHUNK_MAP::const_iterator it, it_end;
366
367 for( it = m_freeChunks.begin(), it_end = m_freeChunks.end(); it != it_end; ++it )
368 {
369 freeChunks.emplace_back( it->second, it->first );
370 }
371
372 m_freeChunks.clear();
373 freeChunks.sort();
374
375 std::list<CHUNK>::const_iterator itf, itf_end;
376 unsigned int offset = freeChunks.front().first;
377 unsigned int size = freeChunks.front().second;
378 freeChunks.pop_front();
379
380 for( itf = freeChunks.begin(), itf_end = freeChunks.end(); itf != itf_end; ++itf )
381 {
382 if( itf->first == offset + size )
383 {
384 // These chunks can be merged, so just increase the current chunk size and go on
385 size += itf->second;
386 }
387 else
388 {
389 // These chunks cannot be merged
390 // So store the previous one
391 m_freeChunks.insert( std::make_pair( size, offset ) );
392
393 // and let's check the next chunk
394 offset = itf->first;
395 size = itf->second;
396 }
397 }
398
399 // Add the last one
400 m_freeChunks.insert( std::make_pair( size, offset ) );
401
402#if CACHED_CONTAINER_TEST > 0
403 test();
404#endif
405}
406
407
408void CACHED_CONTAINER::addFreeChunk( unsigned int aOffset, unsigned int aSize )
409{
410 assert( aOffset + aSize <= m_currentSize );
411 assert( aSize > 0 );
412
413 m_freeChunks.insert( std::make_pair( aSize, aOffset ) );
414 m_freeSpace += aSize;
415}
416
417
421
422
426
427
429{
430#ifdef KICAD_GAL_PROFILE
431 // Free space check
432 unsigned int freeSpace = 0;
433 FREE_CHUNK_MAP::iterator itf;
434
435 for( itf = m_freeChunks.begin(); itf != m_freeChunks.end(); ++itf )
436 freeSpace += getChunkSize( *itf );
437
438 assert( freeSpace == m_freeSpace );
439
440 // Used space check
441 unsigned int used_space = 0;
442 ITEMS::iterator itr;
443
444 for( itr = m_items.begin(); itr != m_items.end(); ++itr )
445 used_space += ( *itr )->GetSize();
446
447 // If we have a chunk assigned, then there must be an item edited
448 assert( m_chunkSize == 0 || m_item );
449
450 // Currently reserved chunk is also counted as used
451 used_space += m_chunkSize;
452
453 assert( ( m_freeSpace + used_space ) == m_currentSize );
454
455 // Overlapping check TODO
456#endif /* KICAD_GAL_PROFILE */
457}
unsigned int m_chunkOffset
Maximal vertex index number stored in the container.
void addFreeChunk(unsigned int aOffset, unsigned int aSize)
Add a chunk marked as a free space.
int getChunkSize(const CHUNK &aChunk) const
Return the size of a chunk.
void mergeFreeChunks()
Look for consecutive free memory chunks and merges them, decreasing fragmentation of memory.
void showFreeChunks()
Debug & test functions.
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...
virtual void FinishItem() override
Clean up after adding an item.
virtual void Delete(VERTEX_ITEM *aItem) override
Remove all data stored in the container and restores its original state.
unsigned int getChunkOffset(const CHUNK &aChunk) const
Return the offset of a chunk.
virtual VERTEX * Allocate(unsigned int aSize) override
Return allocated space for the requested number of vertices associated with the current item (set wit...
virtual void Clear() override
Remove all data stored in the container and restores its original state.
virtual bool defragmentResize(unsigned int aNewSize)=0
Remove empty spaces between chunks and optionally resizes the container.
virtual void SetItem(VERTEX_ITEM *aItem) override
Clean up after adding an item.
CACHED_CONTAINER(unsigned int aSize=DEFAULT_SIZE)
virtual bool IsMapped() const =0
Return true if vertex buffer is currently mapped.
ITEMS m_items
Currently modified item.
bool reallocate(unsigned int aSize)
Resize the chunk that stores the current item to the given size.
unsigned int m_initialSize
Actual storage 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.
VERTEX_CONTAINER(unsigned int aSize=DEFAULT_SIZE)
bool m_dirty
Default initial size of a container (expressed in vertices)
void setSize(unsigned int aSize)
Set data size in the container.
Definition vertex_item.h:94
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 small class to help profiling.
Definition profile.h:49
The Cairo implementation of the graphics abstraction layer.
Definition eda_group.h:33
static constexpr size_t VERTEX_SIZE
wxString result
Test unit parsing edge cases and error handling.
Class to handle an item held in a container.