CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/GPU/OpenGL/GLMemory.cpp
Views: 1401
1
#include "Common/MemoryUtil.h"
2
#include "Common/GPU/OpenGL/GLMemory.h"
3
#include "Common/GPU/OpenGL/GLRenderManager.h"
4
#include "Common/GPU/OpenGL/GLFeatures.h"
5
#include "Common/Data/Text/Parsers.h"
6
7
extern std::thread::id renderThreadId;
8
#if MAX_LOGLEVEL >= DEBUG_LEVEL
9
static bool OnRenderThread() {
10
return std::this_thread::get_id() == renderThreadId;
11
}
12
#endif
13
14
void *GLRBuffer::Map(GLBufferStrategy strategy) {
15
_assert_(buffer_ != 0);
16
17
GLbitfield access = GL_MAP_WRITE_BIT;
18
if ((strategy & GLBufferStrategy::MASK_FLUSH) != 0) {
19
access |= GL_MAP_FLUSH_EXPLICIT_BIT;
20
}
21
if ((strategy & GLBufferStrategy::MASK_INVALIDATE) != 0) {
22
access |= GL_MAP_INVALIDATE_BUFFER_BIT;
23
}
24
25
void *p = nullptr;
26
bool allowNativeBuffer = strategy != GLBufferStrategy::SUBDATA;
27
if (allowNativeBuffer) {
28
glBindBuffer(target_, buffer_);
29
30
if (gl_extensions.ARB_buffer_storage || gl_extensions.EXT_buffer_storage) {
31
#if !PPSSPP_PLATFORM(IOS)
32
if (!hasStorage_) {
33
GLbitfield storageFlags = access & ~(GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
34
#ifdef USING_GLES2
35
#ifdef GL_EXT_buffer_storage
36
glBufferStorageEXT(target_, size_, nullptr, storageFlags);
37
#endif
38
#else
39
glBufferStorage(target_, size_, nullptr, storageFlags);
40
#endif
41
hasStorage_ = true;
42
}
43
#endif
44
p = glMapBufferRange(target_, 0, size_, access);
45
} else if (gl_extensions.VersionGEThan(3, 0, 0)) {
46
// GLES3 or desktop 3.
47
p = glMapBufferRange(target_, 0, size_, access);
48
} else if (!gl_extensions.IsGLES) {
49
#ifndef USING_GLES2
50
p = glMapBuffer(target_, GL_READ_WRITE);
51
#endif
52
}
53
}
54
55
mapped_ = p != nullptr;
56
return p;
57
}
58
59
bool GLRBuffer::Unmap() {
60
glBindBuffer(target_, buffer_);
61
mapped_ = false;
62
return glUnmapBuffer(target_) == GL_TRUE;
63
}
64
65
GLPushBuffer::GLPushBuffer(GLRenderManager *render, GLuint target, size_t size, const char *tag) : render_(render), size_(size), target_(target), tag_(tag) {
66
AddBuffer();
67
RegisterGPUMemoryManager(this);
68
}
69
70
GLPushBuffer::~GLPushBuffer() {
71
UnregisterGPUMemoryManager(this);
72
Destroy(true);
73
}
74
75
void GLPushBuffer::Map() {
76
_assert_(!writePtr_);
77
auto &info = buffers_[buf_];
78
writePtr_ = info.deviceMemory ? info.deviceMemory : info.localMemory;
79
info.flushOffset = 0;
80
// Force alignment. This is needed for PushAligned() to work as expected.
81
while ((intptr_t)writePtr_ & 15) {
82
writePtr_++;
83
offset_++;
84
info.flushOffset++;
85
}
86
_assert_(writePtr_);
87
}
88
89
void GLPushBuffer::Unmap() {
90
_assert_(writePtr_);
91
if (!buffers_[buf_].deviceMemory) {
92
// Here we simply upload the data to the last buffer.
93
// Might be worth trying with size_ instead of offset_, so the driver can replace
94
// the whole buffer. At least if it's close.
95
render_->BufferSubdata(buffers_[buf_].buffer, 0, offset_, buffers_[buf_].localMemory, false);
96
} else {
97
buffers_[buf_].flushOffset = offset_;
98
}
99
writePtr_ = nullptr;
100
}
101
102
void GLPushBuffer::Flush() {
103
// Must be called from the render thread.
104
_dbg_assert_(OnRenderThread());
105
if (buf_ >= buffers_.size()) {
106
_dbg_assert_msg_(false, "buf_ somehow got out of sync: %d vs %d", (int)buf_, (int)buffers_.size());
107
return;
108
}
109
110
buffers_[buf_].flushOffset = offset_;
111
if (!buffers_[buf_].deviceMemory && writePtr_) {
112
auto &info = buffers_[buf_];
113
if (info.flushOffset != 0) {
114
_assert_(info.buffer->buffer_);
115
glBindBuffer(target_, info.buffer->buffer_);
116
glBufferSubData(target_, 0, info.flushOffset, info.localMemory);
117
}
118
119
// Here we will submit all the draw calls, with the already known buffer and offsets.
120
// Might as well reset the write pointer here and start over the current buffer.
121
writePtr_ = info.localMemory;
122
offset_ = 0;
123
info.flushOffset = 0;
124
}
125
126
// For device memory, we flush all buffers here.
127
if ((strategy_ & GLBufferStrategy::MASK_FLUSH) != 0) {
128
for (auto &info : buffers_) {
129
if (info.flushOffset == 0 || !info.deviceMemory)
130
continue;
131
132
glBindBuffer(target_, info.buffer->buffer_);
133
glFlushMappedBufferRange(target_, 0, info.flushOffset);
134
info.flushOffset = 0;
135
}
136
}
137
}
138
139
void GLPushBuffer::AddBuffer() {
140
// INFO_LOG(Log::G3D, "GLPushBuffer(%s): Allocating %d bytes", tag_, size_);
141
BufInfo info;
142
info.localMemory = (uint8_t *)AllocateAlignedMemory(size_, 16);
143
_assert_msg_(info.localMemory != 0, "GLPushBuffer alloc fail: %d (%s)", (int)size_, tag_);
144
info.buffer = render_->CreateBuffer(target_, size_, GL_DYNAMIC_DRAW);
145
info.size = size_;
146
buf_ = buffers_.size();
147
buffers_.push_back(info);
148
}
149
150
void GLPushBuffer::Destroy(bool onRenderThread) {
151
if (buf_ == -1)
152
return; // Already destroyed
153
for (BufInfo &info : buffers_) {
154
// This will automatically unmap device memory, if needed.
155
// NOTE: We immediately delete the buffer, don't go through the deleter, if we're on the render thread.
156
if (onRenderThread) {
157
delete info.buffer;
158
} else {
159
render_->DeleteBuffer(info.buffer);
160
}
161
FreeAlignedMemory(info.localMemory);
162
}
163
buffers_.clear();
164
buf_ = -1;
165
}
166
167
void GLPushBuffer::NextBuffer(size_t minSize) {
168
// First, unmap the current memory.
169
Unmap();
170
171
buf_++;
172
if (buf_ >= buffers_.size() || minSize > size_) {
173
// Before creating the buffer, adjust to the new size_ if necessary.
174
while (size_ < minSize) {
175
size_ <<= 1;
176
}
177
AddBuffer();
178
}
179
180
// Now, move to the next buffer and map it.
181
offset_ = 0;
182
Map();
183
}
184
185
void GLPushBuffer::Defragment() {
186
_dbg_assert_msg_(!OnRenderThread(), "Defragment must not run on the render thread");
187
188
if (buffers_.size() <= 1) {
189
// Let's take this opportunity to jettison any localMemory we don't need.
190
for (auto &info : buffers_) {
191
if (info.deviceMemory) {
192
FreeAlignedMemory(info.localMemory);
193
info.localMemory = nullptr;
194
}
195
}
196
return;
197
}
198
199
// Okay, we have more than one. Destroy them all and start over with a larger one.
200
201
// When calling AddBuffer, we sometimes increase size_. So if we allocated multiple buffers in a frame,
202
// they won't all have the same size. Sum things up properly.
203
size_t newSize = 0;
204
for (int i = 0; i < (int)buffers_.size(); i++) {
205
newSize += buffers_[i].size;
206
}
207
208
Destroy(false);
209
210
// Set some sane but very free limits. If there's another spike, we'll just allocate more anyway.
211
size_ = std::min(std::max(newSize, (size_t)65536), (size_t)(512 * 1024 * 1024));
212
AddBuffer();
213
}
214
215
size_t GLPushBuffer::GetTotalSize() const {
216
size_t sum = 0;
217
// When calling AddBuffer, we sometimes increase size_. So if we allocated multiple buffers in a frame,
218
// they won't all have the same size. Sum things up properly.
219
if (buffers_.size() > 1) {
220
for (int i = 0; i < (int)buffers_.size() - 1; i++) {
221
sum += buffers_[i].size;
222
}
223
}
224
sum += offset_;
225
return sum;
226
}
227
228
void GLPushBuffer::MapDevice(GLBufferStrategy strategy) {
229
_dbg_assert_msg_(OnRenderThread(), "MapDevice must run on render thread");
230
231
strategy_ = strategy;
232
if (strategy_ == GLBufferStrategy::SUBDATA) {
233
return;
234
}
235
236
bool mapChanged = false;
237
for (auto &info : buffers_) {
238
if (!info.buffer->buffer_ || info.deviceMemory) {
239
// Can't map - no device buffer associated yet or already mapped.
240
continue;
241
}
242
243
info.deviceMemory = (uint8_t *)info.buffer->Map(strategy_);
244
mapChanged = mapChanged || info.deviceMemory != nullptr;
245
246
if (!info.deviceMemory && !info.localMemory) {
247
// Somehow it failed, let's dodge crashing.
248
info.localMemory = (uint8_t *)AllocateAlignedMemory(info.buffer->size_, 16);
249
mapChanged = true;
250
}
251
252
_dbg_assert_msg_(info.localMemory || info.deviceMemory, "Local or device memory must succeed");
253
}
254
255
if (writePtr_ && mapChanged) {
256
// This can happen during a sync. Remap.
257
writePtr_ = nullptr;
258
Map();
259
}
260
}
261
262
void GLPushBuffer::UnmapDevice() {
263
_dbg_assert_msg_(OnRenderThread(), "UnmapDevice must run on render thread");
264
265
for (auto &info : buffers_) {
266
if (info.deviceMemory) {
267
// TODO: Technically this can return false?
268
info.buffer->Unmap();
269
info.deviceMemory = nullptr;
270
}
271
}
272
}
273
274
void GLPushBuffer::GetDebugString(char *buffer, size_t bufSize) const {
275
snprintf(buffer, bufSize, "%s: %s/%s (%d)", tag_, NiceSizeFormat(this->offset_).c_str(), NiceSizeFormat(this->size_).c_str(), (int)buffers_.size());
276
}
277
278