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( ITEMS::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 ITEMS::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()],
324 m_item->GetSize() * VERTEX_SIZE );
325 m_item->setOffset( newOffset );
326 m_chunkOffset = newOffset;
327 }
328 }
329#ifdef __WIN32__
330 #ifdef __MINGW32__
331 // currently, because SEH (Structured Exception Handling) is not documented on msys
332 // (for instance __except1 exists without doc) or is not supported, do nothing
333 #else
334 __except( GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER
335 : EXCEPTION_CONTINUE_SEARCH )
336 {
337 throw std::runtime_error(
338 "Access violation in defragment. This is usually an indicator of "
339 "system or GPU memory running low." );
340 };
341 #endif
342#endif
343 }();
344
346}
347
348
350{
351 if( m_freeChunks.size() <= 1 ) // There are no chunks that can be merged
352 return;
353
354#ifdef KICAD_GAL_PROFILE
355 PROF_TIMER totalTime;
356#endif /* KICAD_GAL_PROFILE */
357
358 // Reversed free chunks map - this one stores chunk size with its offset as the key
359 std::list<CHUNK> freeChunks;
360
361 FREE_CHUNK_MAP::const_iterator it, it_end;
362
363 for( it = m_freeChunks.begin(), it_end = m_freeChunks.end(); it != it_end; ++it )
364 {
365 freeChunks.emplace_back( it->second, it->first );
366 }
367
368 m_freeChunks.clear();
369 freeChunks.sort();
370
371 std::list<CHUNK>::const_iterator itf, itf_end;
372 unsigned int offset = freeChunks.front().first;
373 unsigned int size = freeChunks.front().second;
374 freeChunks.pop_front();
375
376 for( itf = freeChunks.begin(), itf_end = freeChunks.end(); itf != itf_end; ++itf )
377 {
378 if( itf->first == offset + size )
379 {
380 // These chunks can be merged, so just increase the current chunk size and go on
381 size += itf->second;
382 }
383 else
384 {
385 // These chunks cannot be merged
386 // So store the previous one
387 m_freeChunks.insert( std::make_pair( size, offset ) );
388
389 // and let's check the next chunk
390 offset = itf->first;
391 size = itf->second;
392 }
393 }
394
395 // Add the last one
396 m_freeChunks.insert( std::make_pair( size, offset ) );
397
398#if CACHED_CONTAINER_TEST > 0
399 test();
400#endif
401}
402
403
404void CACHED_CONTAINER::addFreeChunk( unsigned int aOffset, unsigned int aSize )
405{
406 assert( aOffset + aSize <= m_currentSize );
407 assert( aSize > 0 );
408
409 m_freeChunks.insert( std::make_pair( aSize, aOffset ) );
410 m_freeSpace += aSize;
411}
412
413
417
418
422
423
425{
426#ifdef KICAD_GAL_PROFILE
427 // Free space check
428 unsigned int freeSpace = 0;
429 FREE_CHUNK_MAP::iterator itf;
430
431 for( itf = m_freeChunks.begin(); itf != m_freeChunks.end(); ++itf )
432 freeSpace += getChunkSize( *itf );
433
434 assert( freeSpace == m_freeSpace );
435
436 // Used space check
437 unsigned int used_space = 0;
438 ITEMS::iterator itr;
439
440 for( itr = m_items.begin(); itr != m_items.end(); ++itr )
441 used_space += ( *itr )->GetSize();
442
443 // If we have a chunk assigned, then there must be an item edited
444 assert( m_chunkSize == 0 || m_item );
445
446 // Currently reserved chunk is also counted as used
447 used_space += m_chunkSize;
448
449 assert( ( m_freeSpace + used_space ) == m_currentSize );
450
451 // Overlapping check TODO
452#endif /* KICAD_GAL_PROFILE */
453}
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: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:29
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.