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
31namespace love
32{
33namespace graphics
34{
35namespace opengl
36{
37
38Buffer::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
66Buffer::~Buffer()
67{
68 if (vbo != 0)
69 unload();
70
71 delete[] memory_map;
72}
73
74void *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
87void 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
97void 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
115void 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
163void 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
175void 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
188ptrdiff_t Buffer::getHandle() const
189{
190 return vbo;
191}
192
193void 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
198bool Buffer::loadVolatile()
199{
200 return load(true);
201}
202
203void Buffer::unloadVolatile()
204{
205 unload();
206}
207
208bool 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
225void Buffer::unload()
226{
227 is_mapped = false;
228 gl.deleteBuffer(vbo);
229 vbo = 0;
230}
231
232} // opengl
233} // graphics
234} // love
235