|
| 1 | +--- |
| 2 | +title: Memory Buffers |
| 3 | +description: Large memory buffers to store values. |
| 4 | +--- |
| 5 | + |
| 6 | +Since Milkdrop 2, a new feature to store values called "megabuf" was added. It adds several memory buffers which can be |
| 7 | +used in all expressions, with each buffer being able to store up to 8,388,608 numbers. |
| 8 | + |
| 9 | +Each execution context in Milkdrop has its own memory buffer, plus an additional one called "gmegabuf" that is globally |
| 10 | +available across all code blocks. Execution contexts are generally tied to a specific effect. This is a list of all |
| 11 | +10 contexts and which ones share a megabuf, with one context per line: |
| 12 | + |
| 13 | +- `per_frame_init_` and `per_frame_` |
| 14 | +- `per_pixel_` |
| 15 | +- `wave_1_init`, `wave_1_per_frame` and `wave_1_per_point` |
| 16 | +- `wave_2_init`, `wave_2_per_frame` and `wave_2_per_point` |
| 17 | +- `wave_3_init`, `wave_3_per_frame` and `wave_3_per_point` |
| 18 | +- `wave_4_init`, `wave_4_per_frame` and `wave_4_per_point` |
| 19 | +- `shape_1_init` and `shape_1_per_frame` |
| 20 | +- `shape_2_init` and `shape_2_per_frame` |
| 21 | +- `shape_3_init` and `shape_3_per_frame` |
| 22 | +- `shape_4_init` and `shape_4_per_frame` |
| 23 | + |
| 24 | +This means there are 11 separate buffers per preset, allowing to store 92,274,688 different values. Using all slots |
| 25 | +would require 704 MB of RAM (numbers are stored as 64-bit/8-byte double-precision floats internally). |
| 26 | + |
| 27 | +The global memory buffer can be used to circumvent the way of how values are passed using `qNN` and `tN` variables, |
| 28 | +similar to how `regNN` variables work, but having a larger capacity. Since this buffer is also not touched between |
| 29 | +frames, it can be used to carry values from an effect late in a frame to another effect drawn in the next frame, like |
| 30 | +custom waveform/spectrum values to be used in custom shapes. While this will have a single frame of lag, it opens up a |
| 31 | +few additional possibilities such as drawing spectrum data using custom shapes or the warp grid. |
| 32 | + |
| 33 | +Warp and composite shaders do not have access to these buffers, as they run on the GPU. |
| 34 | + |
| 35 | +## Buffer Allocation |
| 36 | + |
| 37 | +To reduce the number of memory allocations at runtime, values are not individually allocated in system memory. Rather, |
| 38 | +Milkdrop and projectM allocate up to 128 blocks of 65,536 values per buffer. |
| 39 | + |
| 40 | +Each time a value index is accessed, it is checked whether the block this index is stored is already allocated. If it |
| 41 | +is, the existing block will be accessed. If not, a new memory block is allocated and filled with zeroes. |
| 42 | + |
| 43 | +### Memory Usage Warning |
| 44 | + |
| 45 | +This in turn means if a preset accesses the first index of each block once, _all_ memory block would be allocated. In |
| 46 | +theory, the original ns-eel2 interpreter in Milkdrop supports setting a memory limit, but this feature is not used and |
| 47 | +thus there is no memory cap except the size of all buffers combined. |
| 48 | + |
| 49 | +When writing presets, it's highly recommended to not spread memory indices too far, e.g. trying to keep them within one |
| 50 | +or two memory blocks if possible. In practice, most presets will probably only make use of the global buffer, ignoring |
| 51 | +the context-specific ones altogether. On modern PCs, a few MB of RAM usage won't have a big impact anyway. |
| 52 | + |
| 53 | +## Accessing Memory Buffers |
| 54 | + |
| 55 | +The global and context-specific memory buffers can be accessed with different methods. |
| 56 | + |
| 57 | +### megabuf() |
| 58 | + |
| 59 | +The `megabuf()` functions returns a reference to a single value in the memory buffer of the current execution context. |
| 60 | +The return value is a reference, and thus can be both read and assigned to: |
| 61 | + |
| 62 | +``` |
| 63 | +x = megabuf(1000); |
| 64 | +megabuf(1001) = x + 1; |
| 65 | +``` |
| 66 | + |
| 67 | +### gmegabuf() |
| 68 | + |
| 69 | +The `gmegabuf()` functions returns a reference to a single value in the preset-global memory buffer. The return value is |
| 70 | +a reference, and thus can be both read and assigned to: |
| 71 | + |
| 72 | +``` |
| 73 | +x = gmegabuf(1000); |
| 74 | +gmegabuf(1001) = x + 1; |
| 75 | +``` |
| 76 | + |
| 77 | +### Subscript Operator |
| 78 | + |
| 79 | +The subscript operator or "array access operator" has a slightly different meaning than in other programming languages. |
| 80 | +The Milkdrop expression syntax only knows a single data type, individual numbers, so there are no arrays. |
| 81 | + |
| 82 | +If any index value is not an integer, it is _rounded_ to the nearest integer after calculating the final index (e.g. |
| 83 | +adding index and offset first, then rounding). |
| 84 | + |
| 85 | +The subscript operator is instead used to address memory locations in megabuf and gmegabuf. There are three possible |
| 86 | +syntax variants: |
| 87 | + |
| 88 | +#### Global Memory Access |
| 89 | + |
| 90 | +Accessing the gmegabuf is possible by using the special keyword `gmem`, followed by an index in the subscript brackets. |
| 91 | +The following statement sets gmegabuf location 10.000 to the current value of `x`: |
| 92 | + |
| 93 | +``` |
| 94 | +gmem[10000] = x; |
| 95 | +``` |
| 96 | + |
| 97 | +Any memory index from 0 to 8.388.607 (= 128 * 65536) can be addressed. |
| 98 | + |
| 99 | +#### Local Memory Access |
| 100 | + |
| 101 | +Accessing the current context memory buffer (megabuf) can be done by writing the index before a set of empty brackets. |
| 102 | +The following example is analogous to the above, but it sets index 10.000 of the local memory instead: |
| 103 | + |
| 104 | +``` |
| 105 | +10000[] = x; |
| 106 | +``` |
| 107 | + |
| 108 | +#### Local Memory Access with Offset |
| 109 | + |
| 110 | +An optional offset can be provided in the brackets, which is simply added to the index on the outside. The following |
| 111 | +example will set memory index 10.123 to the value of `x`: |
| 112 | + |
| 113 | +``` |
| 114 | +10000[123] = x; |
| 115 | +``` |
| 116 | + |
| 117 | +Both index and offset values can of course be calculated with expressions. So the following expression is valid: |
| 118 | + |
| 119 | +``` |
| 120 | +if(x > 5,5000,1000)[(sin(y) + 1 * .5) * 1000] = z; |
| 121 | +``` |
| 122 | + |
| 123 | +## Initializing a Range |
| 124 | + |
| 125 | +**Note:** This works only on context-specific memory buffers, _not_ gmegabuf! |
| 126 | + |
| 127 | +If a large portion of a context-specific memory buffer needs to be initialized with a specific, single value, the |
| 128 | +`memset` function can be used instead of `loop` and iterating over all indices. The following lines are equivalent, with |
| 129 | +the second one being more performant - both set 1000 values to 1.123, starting at index 4000: |
| 130 | + |
| 131 | +``` |
| 132 | +idx=0; loop(1000, 4000[idx] = 1.123; idx += 1); |
| 133 | +memset(4000, 1.123, 1000); |
| 134 | +``` |
| 135 | + |
| 136 | +## Copying Ranges |
| 137 | + |
| 138 | +**Note:** This works only on context-specific memory buffers, _not_ gmegabuf! There is no way of either copying ranges |
| 139 | +within gmegabuf or between gmegabuf and a context megabuf. |
| 140 | + |
| 141 | +In the case a large, continuous portion of values need to be copied, the `loop` function in expression code is way |
| 142 | +slower than using [the `memcpy`function](functions#memcpydest-src-count). This function takes a destination index, a |
| 143 | +source index and a count as arguments and then copies `count` values starting at the source index to the destination |
| 144 | +index. |
| 145 | + |
| 146 | +To copy 1000 values from index 2000 to 4000, both of the following lines would do this, with the second one being |
| 147 | +way more performant: |
| 148 | + |
| 149 | +``` |
| 150 | +idx=0; loop(1000, 4000[idx] = 2000[idx]; idx += 1); |
| 151 | +memcpy(4000, 2000, 1000); |
| 152 | +``` |
| 153 | + |
| 154 | +## Freeing Memory |
| 155 | + |
| 156 | +While the expression language defines a function [`freembuf`](functions#freembufindex), it doesn't actually do anything |
| 157 | +in Milkdrop or projectM. Once memory has been allocated, it'll stay that way until either the preset is unloaded |
| 158 | +(context-specific buffers and gmegabuf in projectM) or Milkdrop is closed (gmegabuf in Milkdrop). |
| 159 | + |
| 160 | +## Cross-Preset Sharing |
| 161 | + |
| 162 | +In the original Milkdrop and most derivatives, the global memory buffer is shared across presets being blended. While |
| 163 | +rare, this can occasionally break presets making heavy use of gmegabuf, as the newly loaded preset may use the same |
| 164 | +buffer indices as the previous one. |
| 165 | + |
| 166 | +projectM does use one gmegabuf instance per preset, so each preset is fully independent regarding gmegabuf use. |
0 commit comments