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 assert( m_items.find( aItem ) != m_items.end() || aItem->GetSize() == 0 );
161
162 int size = aItem->GetSize();
163
164 if( size == 0 )
165 return; // Item is not stored here
166
167 int offset = aItem->GetOffset();
168
169 // Insert a free memory chunk entry in the place where item was stored
170 addFreeChunk( offset, size );
171
172 // Indicate that the item is not stored in the container anymore
173 aItem->setSize( 0 );
174
175 m_items.erase( aItem );
176
177#if CACHED_CONTAINER_TEST > 0
178 test();
179#endif
180
181 // This dynamic memory freeing optimize memory usage, but in fact can create
182 // out of memory issues because freeing and reallocation large chunks of memory
183 // can create memory fragmentation and no room to reallocate large chunks
184 // after many free/reallocate cycles during a session using the same complex board
185 // So it can be disable.
186 // Currently: it is disable to avoid "out of memory" issues
187#if 0
188 // Dynamic memory freeing, there is no point in holding
189 // a large amount of memory when there is no use for it
191 {
193 }
194#endif
195}
196
197
199{
201 m_maxIndex = 0;
202 m_failed = false;
203
204 // Set the size of all the stored VERTEX_ITEMs to 0, so it is clear that they are not held
205 // in the container anymore
206 for( std::set<VERTEX_ITEM*>::iterator it = m_items.begin(); it != m_items.end(); ++it )
207 ( *it )->setSize( 0 );
208
209 m_items.clear();
210
211 // Now there is only free space left
212 m_freeChunks.clear();
213 m_freeChunks.insert( std::make_pair( m_freeSpace, 0 ) );
214}
215
216
217bool CACHED_CONTAINER::reallocate( unsigned int aSize )
218{
219 assert( aSize > 0 );
220 assert( IsMapped() );
221
222 unsigned int itemSize = m_item->GetSize();
223
224 // Find a free space chunk >= aSize
225 FREE_CHUNK_MAP::iterator newChunk = m_freeChunks.lower_bound( aSize );
226
227 // Is there enough space to store vertices?
228 if( newChunk == m_freeChunks.end() )
229 {
230 bool result;
231
232 // Would it be enough to double the current space?
233 if( aSize < m_freeSpace + m_currentSize )
234 {
235 // Yes: exponential growing
237 }
238 else
239 {
240 // No: grow to the nearest greater power of 2
241 result = defragmentResize( pow( 2, ceil( log2( m_currentSize * 2 + aSize ) ) ) );
242 }
243
244 if( !result )
245 return false;
246
247 newChunk = m_freeChunks.lower_bound( aSize );
248 assert( newChunk != m_freeChunks.end() );
249 }
250
251 // Parameters of the allocated chunk
252 unsigned int newChunkSize = getChunkSize( *newChunk );
253 unsigned int newChunkOffset = getChunkOffset( *newChunk );
254
255 assert( newChunkSize >= aSize );
256 assert( newChunkOffset < m_currentSize );
257
258 // Check if the item was previously stored in the container
259 if( itemSize > 0 )
260 {
261 // Safety check: m_vertices must be valid at this point. On some platforms (notably
262 // macOS with Metal-backed OpenGL), the GPU buffer mapping can silently fail or be
263 // invalidated under memory pressure, leaving m_vertices null even after a successful
264 // defragmentResize(). Bail out rather than crash on the memcpy.
265 if( !m_vertices )
266 return false;
267
268 // The item was reallocated, so we have to copy all the old data to the new place
269 memcpy( &m_vertices[newChunkOffset], &m_vertices[m_chunkOffset], itemSize * VERTEX_SIZE );
270
271 // Free the space used by the previous chunk
273 }
274
275 // Remove the new allocated chunk from the free space pool
276 m_freeChunks.erase( newChunk );
277 m_freeSpace -= newChunkSize;
278
279 m_chunkSize = newChunkSize;
280 m_chunkOffset = newChunkOffset;
281
282 m_item->setOffset( m_chunkOffset );
283
284 return true;
285}
286
287
289{
290 // Defragmentation
291 std::set<VERTEX_ITEM*>::iterator it, it_end;
292 int newOffset = 0;
293
294 [&]()
295 {
296#ifdef __WIN32__
297 #ifdef __MINGW32__
298 // currently, because SEH (Structured Exception Handling) is not documented on msys
299 // (for instance __try or __try1 exists without doc) or is not supported, do nothing
300 #else
301 __try
302 #endif
303#endif
304 {
305 for( VERTEX_ITEM* item : m_items )
306 {
307 int itemOffset = item->GetOffset();
308 int itemSize = item->GetSize();
309
310 // Move an item to the new container
311 memcpy( &aTarget[newOffset], &m_vertices[itemOffset], itemSize * VERTEX_SIZE );
312
313 // Update new offset
314 item->setOffset( newOffset );
315
316 // Move to the next free space
317 newOffset += itemSize;
318 }
319
320 // Move the current item and place it at the end
321 if( m_item->GetSize() > 0 )
322 {
323 memcpy( &aTarget[newOffset], &m_vertices[m_item->GetOffset()], m_item->GetSize() * VERTEX_SIZE );
324 m_item->setOffset( newOffset );
325 m_chunkOffset = newOffset;
326 }
327 }
328#ifdef __WIN32__
329 #ifdef __MINGW32__
330 // currently, because SEH (Structured Exception Handling) is not documented on msys
331 // (for instance __except1 exists without doc) or is not supported, do nothing
332 #else
333 __except( GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER
334 : EXCEPTION_CONTINUE_SEARCH )
335 {
336 throw std::runtime_error(
337 "Access violation in defragment. This is usually an indicator of "
338 "system or GPU memory running low." );
339 };
340 #endif
341#endif
342 }();
343
345}
346
347
349{
350 if( m_freeChunks.size() <= 1 ) // There are no chunks that can be merged
351 return;
352
353#ifdef KICAD_GAL_PROFILE
354 PROF_TIMER totalTime;
355#endif /* KICAD_GAL_PROFILE */
356
357 // Reversed free chunks map - this one stores chunk size with its offset as the key
358 std::list<CHUNK> freeChunks;
359
360 FREE_CHUNK_MAP::const_iterator it, it_end;
361
362 for( it = m_freeChunks.begin(), it_end = m_freeChunks.end(); it != it_end; ++it )
363 {
364 freeChunks.emplace_back( it->second, it->first );
365 }
366
367 m_freeChunks.clear();
368 freeChunks.sort();
369
370 std::list<CHUNK>::const_iterator itf, itf_end;
371 unsigned int offset = freeChunks.front().first;
372 unsigned int size = freeChunks.front().second;
373 freeChunks.pop_front();
374
375 for( itf = freeChunks.begin(), itf_end = freeChunks.end(); itf != itf_end; ++itf )
376 {
377 if( itf->first == offset + size )
378 {
379 // These chunks can be merged, so just increase the current chunk size and go on
380 size += itf->second;
381 }
382 else
383 {
384 // These chunks cannot be merged
385 // So store the previous one
386 m_freeChunks.insert( std::make_pair( size, offset ) );
387
388 // and let's check the next chunk
389 offset = itf->first;
390 size = itf->second;
391 }
392 }
393
394 // Add the last one
395 m_freeChunks.insert( std::make_pair( size, offset ) );
396
397#if CACHED_CONTAINER_TEST > 0
398 test();
399#endif
400}
401
402
403void CACHED_CONTAINER::addFreeChunk( unsigned int aOffset, unsigned int aSize )
404{
405 assert( aOffset + aSize <= m_currentSize );
406 assert( aSize > 0 );
407
408 m_freeChunks.insert( std::make_pair( aSize, aOffset ) );
409 m_freeSpace += aSize;
410}
411
412
416
417
421
422
424{
425#ifdef KICAD_GAL_PROFILE
426 // Free space check
427 unsigned int freeSpace = 0;
428 FREE_CHUNK_MAP::iterator itf;
429
430 for( itf = m_freeChunks.begin(); itf != m_freeChunks.end(); ++itf )
431 freeSpace += getChunkSize( *itf );
432
433 assert( freeSpace == m_freeSpace );
434
435 // Used space check
436 unsigned int used_space = 0;
437 std::set<VERTEX_ITEM*>::iterator itr;
438
439 for( itr = m_items.begin(); itr != m_items.end(); ++itr )
440 used_space += ( *itr )->GetSize();
441
442 // If we have a chunk assigned, then there must be an item edited
443 assert( m_chunkSize == 0 || m_item );
444
445 // Currently reserved chunk is also counted as used
446 used_space += m_chunkSize;
447
448 assert( ( m_freeSpace + used_space ) == m_currentSize );
449
450 // Overlapping check TODO
451#endif /* KICAD_GAL_PROFILE */
452}
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:90
unsigned int GetOffset() const
Return data offset in the container.
Definition vertex_item.h:64
unsigned int GetSize() const
Return information about number of vertices stored.
Definition vertex_item.h:54
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.