-
Notifications
You must be signed in to change notification settings - Fork 4
/
videothreader.cpp
331 lines (285 loc) · 9.04 KB
/
videothreader.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#include "io.h"
#include "os.h"
#include <stdlib.h>
#include <string.h>
namespace {
struct video_thread_frame {
uint8_t* data;
size_t bufsize;
unsigned int width;
unsigned int height;
};
//Variables may be declared in the following ways:
// | Child read | Child write | Parent read | Parent write |
//Shared | Lock | Lock | Lock | Lock |
//Parent-write | Lock | No | Yes | Lock |
//Parent-only | No | No | Yes | Yes |
//Read-only | Yes | No | Yes | No |
//Yes means the action allowed in all cases; No means not allowed; Lock means allowed only if holding the mutex.
//Child-write and child-only is also possible, with the obvious meanings.
class video_thread : public video {
public://since this entire file is private, making it public inside here does no harm
video* next;//Child-only.
mutex lock;//Thread safe.
event* wake_parent;//Thread safe.
event* wake_child;//Thread safe.
//src and dest are used instead of src/dst because they have different length and are therefore easier to identify
unsigned int src_width;//Shared (external).
unsigned int src_height;//Shared (external).
uint8_t src_bpp;//Shared (external).
videoformat src_format;//Shared (external).
//char padding[2];
unsigned int dest_width;//Shared (external).
unsigned int dest_height;//Shared (external).
bool exit;//Shared. Setting this means the child should terminate.
bool draw;//Shared. Setting this means the child should draw a new frame.
bool draw_null;//Shared. Setting this tells the child to not fetch the new frame.
bool draw_idle;//Shared. Setting this means the parent is allowed to proceed.
double vsync;//Parent-write. Setting this nonzero means the parent will wait for the child to finish drawing before itself claiming drawing is done.
double new_vsync;//Shared (external). Must be separate from the above, because the parent must know whether a signal will come.
//TODO: Screenshots
//TODO: Shaders
video_thread_frame buf_this;//Child-only. Currently being drawn to the screen.
video_thread_frame buf_next;//Shared. Next to be drawn to the screen.
video_thread_frame buf_temp;//Parent-only. Frame being created.
//The above three sometimes swap their contents, at which point the access rules for each change as well.
video_thread_frame buf_last;//Shared (external). Used for screenshot taking.
uint32_t features() { return f_sshot_ptr|f_sshot|f_chain; }
//When the child thread is done drawing:
//- lock->lock_wait()
//- Swap buf_next and buf_this
//- Set buf_next_full false
//- lock->unlock()
//- Draw buf_this
//When the parent wants to draw something:
//- Copy to buf_temp, ensure it's packed (reallocate if needed)
//- Clone buf_temp to buf_last
//- If sync: lock->lock_wait()
//- Else: lock->lock()
//- Swap buf_next and buf_this, whether buf_next_full is set or not
//- Set buf_next_full
//- lock->unlock()
/*private*/ void threadproc()
{
this->next->initialize();
unsigned int src_width=0;
unsigned int src_height=0;
videoformat src_format=fmt_none;
unsigned int dest_width=0;
unsigned int dest_height=0;
//not calling set_source and set_dest here
this->wake_parent->signal();
double vsync=0;
while (true)
{
this->wake_child->wait();
this->lock.lock();
if (this->exit)
{
delete this->next;
this->exit=false;
this->wake_parent->signal();
this->lock.unlock();
return;
}
bool draw=this->draw;
this->draw=false;
bool draw_null=this->draw_null;
bool set_vsync=(vsync!=this->vsync);
vsync=this->vsync;
bool set_src=(src_width!=this->src_width || src_height!=this->src_height || src_format!=this->src_format);
src_width=this->src_width;
src_height=this->src_height;
src_format=this->src_format;
bool set_dest=(dest_width!=this->dest_width || dest_height!=this->dest_height);
dest_width=this->dest_width;
dest_height=this->dest_height;
video_thread_frame buf;
if (draw && !draw_null)
{
buf=this->buf_next;
this->buf_next=this->buf_this;
this->buf_this=buf;
}
this->lock.unlock();
if (set_src) this->next->set_source(src_width, src_height, src_format);
if (set_dest) this->next->set_dest_size(dest_width, dest_height);
if (set_vsync) this->next->set_vsync(vsync);
if (draw)
{
if (draw_null) this->next->draw_repeat();
else this->next->draw_2d(buf.width, buf.height, buf.data, this->src_bpp*buf.width);
}
if (vsync!=0)
{
this->lock.lock();
this->draw_idle=true;
this->lock.unlock();
this->wake_parent->signal();
}
}
}
/*private*/ void initialize()
{
thread_create(bind_this(&video_thread::threadproc));
this->wake_parent->wait();
}
void set_chain(video* next) { this->next=next; }
void set_source(unsigned int max_width, unsigned int max_height, videoformat format)
{
this->lock.lock();
this->src_format=format;
this->src_bpp=videofmt_byte_per_pixel(format);
this->src_width=max_width;
this->src_height=max_height;
this->lock.unlock();
}
/*private*/ void draw_frame(bool real_frame)
{
this->lock.lock();
if (real_frame)
{
this->buf_last=this->buf_temp;
this->buf_temp=this->buf_next;
this->buf_next=this->buf_last;
}
this->draw=true;
this->draw_null=!real_frame;
this->draw_idle=false;
this->vsync=this->new_vsync;
this->lock.unlock();
this->wake_child->signal();
if (this->vsync!=0)
{
while (true)
{
this->wake_parent->wait();
this->lock.lock();
bool done = this->draw_idle;
this->lock.unlock();
if (done) break;
}
}
}
/*private*/ void assure_bufsize(unsigned int width, unsigned int height)
{
size_t bytes=this->src_bpp*width*height;
if (bytes > this->buf_temp.bufsize)
{
free(this->buf_temp.data);
this->buf_temp.data=malloc(bytes);
this->buf_temp.bufsize=bytes;
}
}
void draw_2d_where(unsigned int width, unsigned int height, void * * data, unsigned int * pitch)
{
assure_bufsize(width, height);
*data=this->buf_temp.data;
*pitch=this->src_bpp*width;
}
void draw_2d(unsigned int width, unsigned int height, const void * data, unsigned int pitch)
{
assure_bufsize(width, height);
this->buf_temp.width=width;
this->buf_temp.height=height;
if (data!=this->buf_temp.data)
{
video::copy_2d(this->buf_temp.data, this->src_bpp*width, data, pitch, this->src_bpp*width, height);
}
draw_frame(true);
}
//bool set_input_3d(struct retro_hw_render_callback* input3d);
//uintptr_t input_3d_get_current_framebuffer();
//funcptr input_3d_get_proc_address(const char *sym);
//void draw_3d(unsigned int width, unsigned int height);
void draw_repeat()
{
draw_frame(false);
}
void set_vsync(double fps)
{
//this protects not against the child thread, but against the parent thread - screenshot/shader/vsync functions can be called by a third thread
this->lock.lock();
this->new_vsync=fps;
this->lock.unlock();
}
//TODO: Enable those.
//virtual bool set_shader(shadertype type, const char * filename) { return false; }
//virtual video_shader_param* get_shader_params() { return NULL; }
//virtual void set_shader_param(unsigned int index, double value) {}
void set_dest_size(unsigned int width, unsigned int height)
{
this->lock.lock();
this->dest_width=width;
this->dest_height=height;
this->lock.unlock();
}
int get_screenshot(struct image * img)
{
//this protects not against the child thread, but against the parent thread - screenshot/shader/vsync functions can be called by a third thread
this->lock.lock();
img->width=this->buf_last.width;
img->height=this->buf_last.height;
img->pitch=this->src_bpp*this->buf_last.width;
img->format=this->src_format;
img->pixels=this->buf_last.data;
return 1;
}
//TODO: Enable.
//int get_screenshot_out(unsigned int * width, unsigned int * height, unsigned int * pitch, unsigned int * depth,
// void* * data, size_t datasize);
void release_screenshot(int ret, void* data)
{
if (!ret) return;
this->lock.unlock();
}
video_thread()
{
this->src_format=fmt_none;
this->src_bpp=0;
this->src_width=0;
this->src_height=0;
this->dest_width=0;
this->dest_height=0;
this->wake_child=new event();
this->wake_parent=new event();
this->exit=false;
this->draw=false;
this->draw_idle=true;
this->vsync=0;
this->new_vsync=0;
this->buf_next.data=NULL;
this->buf_next.bufsize=0;
this->buf_this.data=NULL;
this->buf_this.bufsize=0;
this->buf_temp.data=NULL;
this->buf_temp.bufsize=0;
this->buf_last.data=NULL;
this->buf_last.bufsize=0;
}
~video_thread()
{
this->lock.lock();
this->exit=true;
this->lock.unlock();
this->wake_child->signal();
while (true)
{
this->wake_parent->wait();
this->lock.lock();
bool exit=(!this->exit);
this->lock.unlock();
if (exit) break;
}
delete this->wake_child;
delete this->wake_parent;
free(this->buf_this.data);
free(this->buf_next.data);
free(this->buf_temp.data);
}
};
}
video* video::create_thread()
{
return new video_thread();
}