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