1 | /** |
2 | * Copyright (c) 2006-2023 LOVE Development Team |
3 | * |
4 | * This software is provided 'as-is', without any express or implied |
5 | * warranty. In no event will the authors be held liable for any damages |
6 | * arising from the use of this software. |
7 | * |
8 | * Permission is granted to anyone to use this software for any purpose, |
9 | * including commercial applications, and to alter it and redistribute it |
10 | * freely, subject to the following restrictions: |
11 | * |
12 | * 1. The origin of this software must not be misrepresented; you must not |
13 | * claim that you wrote the original software. If you use this software |
14 | * in a product, an acknowledgment in the product documentation would be |
15 | * appreciated but is not required. |
16 | * 2. Altered source versions must be plainly marked as such, and must not be |
17 | * misrepresented as being the original software. |
18 | * 3. This notice may not be removed or altered from any source distribution. |
19 | **/ |
20 | |
21 | #include "Buffer.h" |
22 | |
23 | #include "common/Exception.h" |
24 | #include "graphics/vertex.h" |
25 | |
26 | #include <cstdlib> |
27 | #include <cstring> |
28 | #include <algorithm> |
29 | #include <limits> |
30 | |
31 | namespace love |
32 | { |
33 | namespace graphics |
34 | { |
35 | namespace opengl |
36 | { |
37 | |
38 | Buffer::Buffer(size_t size, const void *data, BufferType type, vertex::Usage usage, uint32 mapflags) |
39 | : love::graphics::Buffer(size, type, usage, mapflags) |
40 | , vbo(0) |
41 | , memory_map(nullptr) |
42 | , modified_start(std::numeric_limits<size_t>::max()) |
43 | , modified_end(0) |
44 | { |
45 | target = OpenGL::getGLBufferType(type); |
46 | |
47 | try |
48 | { |
49 | memory_map = new char[size]; |
50 | } |
51 | catch (std::bad_alloc &) |
52 | { |
53 | throw love::Exception("Out of memory." ); |
54 | } |
55 | |
56 | if (data != nullptr) |
57 | memcpy(memory_map, data, size); |
58 | |
59 | if (!load(data != nullptr)) |
60 | { |
61 | delete[] memory_map; |
62 | throw love::Exception("Could not load vertex buffer (out of VRAM?)" ); |
63 | } |
64 | } |
65 | |
66 | Buffer::~Buffer() |
67 | { |
68 | if (vbo != 0) |
69 | unload(); |
70 | |
71 | delete[] memory_map; |
72 | } |
73 | |
74 | void *Buffer::map() |
75 | { |
76 | if (is_mapped) |
77 | return memory_map; |
78 | |
79 | is_mapped = true; |
80 | |
81 | modified_start = std::numeric_limits<size_t>::max(); |
82 | modified_end = 0; |
83 | |
84 | return memory_map; |
85 | } |
86 | |
87 | void Buffer::unmapStatic(size_t offset, size_t size) |
88 | { |
89 | if (size == 0) |
90 | return; |
91 | |
92 | // Upload the mapped data to the buffer. |
93 | gl.bindBuffer(type, vbo); |
94 | glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, memory_map + offset); |
95 | } |
96 | |
97 | void Buffer::unmapStream() |
98 | { |
99 | GLenum glusage = OpenGL::getGLBufferUsage(getUsage()); |
100 | |
101 | // "orphan" current buffer to avoid implicit synchronisation on the GPU: |
102 | // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf |
103 | gl.bindBuffer(type, vbo); |
104 | glBufferData(target, (GLsizeiptr) getSize(), nullptr, glusage); |
105 | |
106 | #if LOVE_WINDOWS |
107 | // TODO: Verify that this codepath is a useful optimization. |
108 | if (gl.getVendor() == OpenGL::VENDOR_INTEL) |
109 | glBufferData(target, (GLsizeiptr) getSize(), memory_map, glusage); |
110 | else |
111 | #endif |
112 | glBufferSubData(target, 0, (GLsizeiptr) getSize(), memory_map); |
113 | } |
114 | |
115 | void Buffer::unmap() |
116 | { |
117 | if (!is_mapped) |
118 | return; |
119 | |
120 | if ((map_flags & MAP_EXPLICIT_RANGE_MODIFY) != 0) |
121 | { |
122 | if (modified_end >= modified_start) |
123 | { |
124 | modified_start = std::min(modified_start, getSize() - 1); |
125 | modified_end = std::min(modified_end, getSize() - 1); |
126 | } |
127 | } |
128 | else |
129 | { |
130 | modified_start = 0; |
131 | modified_end = getSize() - 1; |
132 | } |
133 | |
134 | if (modified_end >= modified_start) |
135 | { |
136 | size_t modified_size = (modified_end - modified_start) + 1; |
137 | switch (getUsage()) |
138 | { |
139 | case vertex::USAGE_STATIC: |
140 | unmapStatic(modified_start, modified_size); |
141 | break; |
142 | case vertex::USAGE_STREAM: |
143 | unmapStream(); |
144 | break; |
145 | case vertex::USAGE_DYNAMIC: |
146 | default: |
147 | // It's probably more efficient to treat it like a streaming buffer if |
148 | // at least a third of its contents have been modified during the map(). |
149 | if (modified_size >= getSize() / 3) |
150 | unmapStream(); |
151 | else |
152 | unmapStatic(modified_start, modified_size); |
153 | break; |
154 | } |
155 | } |
156 | |
157 | modified_start = std::numeric_limits<size_t>::max(); |
158 | modified_end = 0; |
159 | |
160 | is_mapped = false; |
161 | } |
162 | |
163 | void Buffer::setMappedRangeModified(size_t offset, size_t modifiedsize) |
164 | { |
165 | if (!is_mapped || !(map_flags & MAP_EXPLICIT_RANGE_MODIFY)) |
166 | return; |
167 | |
168 | // We're being conservative right now by internally marking the whole range |
169 | // from the start of section a to the end of section b as modified if both |
170 | // a and b are marked as modified. |
171 | modified_start = std::min(modified_start, offset); |
172 | modified_end = std::max(modified_end, offset + modifiedsize - 1); |
173 | } |
174 | |
175 | void Buffer::fill(size_t offset, size_t size, const void *data) |
176 | { |
177 | memcpy(memory_map + offset, data, size); |
178 | |
179 | if (is_mapped) |
180 | setMappedRangeModified(offset, size); |
181 | else |
182 | { |
183 | gl.bindBuffer(type, vbo); |
184 | glBufferSubData(target, (GLintptr) offset, (GLsizeiptr) size, data); |
185 | } |
186 | } |
187 | |
188 | ptrdiff_t Buffer::getHandle() const |
189 | { |
190 | return vbo; |
191 | } |
192 | |
193 | void Buffer::copyTo(size_t offset, size_t size, love::graphics::Buffer *other, size_t otheroffset) |
194 | { |
195 | other->fill(otheroffset, size, memory_map + offset); |
196 | } |
197 | |
198 | bool Buffer::loadVolatile() |
199 | { |
200 | return load(true); |
201 | } |
202 | |
203 | void Buffer::unloadVolatile() |
204 | { |
205 | unload(); |
206 | } |
207 | |
208 | bool Buffer::load(bool restore) |
209 | { |
210 | glGenBuffers(1, &vbo); |
211 | gl.bindBuffer(type, vbo); |
212 | |
213 | while (glGetError() != GL_NO_ERROR) |
214 | /* Clear the error buffer. */; |
215 | |
216 | // Copy the old buffer only if 'restore' was requested. |
217 | const GLvoid *src = restore ? memory_map : nullptr; |
218 | |
219 | // Note that if 'src' is '0', no data will be copied. |
220 | glBufferData(target, (GLsizeiptr) getSize(), src, OpenGL::getGLBufferUsage(getUsage())); |
221 | |
222 | return (glGetError() == GL_NO_ERROR); |
223 | } |
224 | |
225 | void Buffer::unload() |
226 | { |
227 | is_mapped = false; |
228 | gl.deleteBuffer(vbo); |
229 | vbo = 0; |
230 | } |
231 | |
232 | } // opengl |
233 | } // graphics |
234 | } // love |
235 | |