OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
videowidget.cpp
Go to the documentation of this file.
1#include "videowidget.h"
2
3#include <Tempest/Painter>
4#include <Tempest/TextCodec>
5#include <Tempest/Log>
6#include <Tempest/Application>
7#include <Tempest/Platform>
8#include <Tempest/Fence>
9
10#include <cstddef>
11
12#include "bink/video.h"
13#include "graphics/shaders.h"
14#include "gamemusic.h"
15#include "gothic.h"
16
17using namespace Tempest;
18
20 Input(zenkit::Read& fin):fin(fin) {}
21
22 void read(void *dest, size_t count) override {
23 if(fin.read(dest,count)!=count)
24 throw std::runtime_error("i/o error");
25 at += count;
26 }
27 void skip(size_t count) override {
28 fin.seek(ptrdiff_t(count), zenkit::Whence::CUR);
29 at += count;
30 }
31 void seek(size_t pos) override {
32 fin.seek(ptrdiff_t(pos), zenkit::Whence::BEG);
33 at = pos;
34 }
35
36 zenkit::Read& fin;
37 size_t at = 0;
38 };
39
40struct VideoWidget::Sound : Tempest::SoundProducer {
41 Sound(SoundContext& c, uint16_t sampleRate, bool isMono)
42 :Tempest::SoundProducer(sampleRate, isMono ? 1 : 2), ctx(c), channels(isMono ? 1 : 2) {
43 }
44 void renderSound(int16_t *out, size_t n) override;
45
47 size_t channels = 2;
48 };
49
51 SoundContext(Context& ctx, SoundDevice& dev, uint16_t sampleRate, bool isMono):
52 ctx(ctx), sampleRate(sampleRate), numChannels(isMono ? 1 : 2) {
53 snd = dev.load(std::unique_ptr<VideoWidget::Sound>(new VideoWidget::Sound(*this,sampleRate,isMono)));
54 }
55
57 snd = SoundEffect();
58 }
59
60 void play() {
61 snd.play();
62 }
63
64 uint64_t tickCount() const {
65 auto smp = processedSamples;
66 return uint64_t(smp*1000)/uint64_t(std::max(sampleRate * numChannels, 1));
67 }
68
69 void pushSamples(const std::vector<float>& s) {
70 std::lock_guard<std::mutex> guard(syncSamples);
71 size_t sz = samples.size();
72 samples.resize(sz+s.size());
73 std::memcpy(samples.data()+sz, s.data(), s.size()*sizeof(s[0]));
74 }
75
77 Tempest::SoundEffect snd;
78 std::mutex syncSamples;
79 std::vector<float> samples;
80
81 uint64_t processedSamples = 0;
82 uint16_t sampleRate = 0;
83 uint16_t numChannels = 0;
84 };
85
86void VideoWidget::Sound::renderSound(int16_t *out, size_t n) {
87 n = n*channels; // stereo
88
89 std::lock_guard<std::mutex> guard(ctx.syncSamples);
90 auto& s = ctx.samples;
91 if(s.size()<n)
92 return;
93 for(size_t i=0; i<n; ++i) {
94 float v = s[i];
95 out[i] = (v < -1.00004566f ? int16_t(-32768) : (v > 1.00001514f ? int16_t(32767) : int16_t(v * 32767.5f)));
96 }
97 ctx.samples.erase(ctx.samples.begin(),ctx.samples.begin()+int(n));
99 }
100
102 Context(std::unique_ptr<zenkit::Read>&& f) : fin(std::move(f)), input(*fin), vid(&input) {
103 sndCtx.resize(vid.audioCount());
104 for(size_t i=0; i<sndCtx.size(); ++i) {
105 auto& aud = vid.audio(uint8_t(i));
106 sndCtx[i].reset(new SoundContext(*this,sndDev,aud.sampleRate,aud.isMono));
107 }
108
109 const float volume = Gothic::inst().settingsGetF("SOUND","soundVolume");
110 sndDev.setGlobalVolume(volume);
111 for(size_t i=0; i<vid.audioCount(); ++i)
112 sndCtx[i]->play();
113
114 for(size_t i=0; i<Resources::MaxFramesInFlight; ++i) {
115 cmd[i] = Resources::device().commandBuffer();
116 }
117
118 frameTime = Application::tickCount();
119 }
120
122 for(auto& i:sync)
123 i.wait();
124 }
125
126 void advance(Tempest::Device& device, uint8_t fId) {
127 auto& f = vid.nextFrame();
128 yuvToRgba(f, device, fId);
129 for(size_t i=0; i<vid.audioCount(); ++i)
130 sndCtx[i]->pushSamples(f.audio(uint8_t(i)).samples);
131
132 const uint64_t destTick = (1000*vid.fps().den*vid.currentFrame())/vid.fps().num;
133 const uint64_t tickVis = Application::tickCount() - frameTime;
134 const uint64_t tickSnd = tickSound();
135 const uint64_t tick = std::min(tickVis, tickSnd);
136
137 if(destTick > tick) {
138 Application::sleep(uint32_t(destTick-tick));
139 return;
140 }
141 }
142
143 uint64_t tickSound() const {
144 uint64_t tickSnd = uint64_t(-1);
145 // return tickSnd;
146
147 for(size_t i=0; i<vid.audioCount(); ++i)
148 tickSnd = std::min(tickSnd, sndCtx[i]->tickCount());
149 return tickSnd;
150 }
151
152 void yuvToRgba(const Bink::Frame& f, Tempest::Device& device, uint8_t fId) {
153 if(uint32_t(frameImg.w())!=f.width() || uint32_t(frameImg.h())!=f.height()) {
154 Resources::recycle(std::move(frameImg));
155 frameImg = device.attachment(Tempest::RGBA8, f.width(), f.height());
156 }
157 sync[fId].wait();
158
159 // alignment for ssbo offsets
160 auto alignBuf = [](size_t size, size_t align) { return (size+align-1) & ~(align-1); };
161
162 auto& planeY = f.plane(0);
163 auto& planeU = f.plane(1);
164 auto& planeV = f.plane(2);
165
166 auto align = std::max<size_t>(device.properties().ssbo.offsetAlign, 4);
167 auto sizeY = alignBuf(f.height()*planeY.stride(), align);
168 auto sizeU = alignBuf((f.height()/2)*planeU.stride(), align);
169 auto sizeV = alignBuf((f.height()/2)*planeV.stride(), align);
170 auto size = sizeY + sizeU + sizeV;
171
172 auto& stage = staging[fId];
173 if(stage.byteSize()!=size) {
174 Resources::recycle(std::move(stage));
175 stage = device.ssbo(BufferHeap::Upload, Uninitialized, size);
176 }
177 stage.update(planeY.data(), 0, (f.height()) *planeY.stride());
178 stage.update(planeU.data(), sizeY, (f.height()/2)*planeU.stride());
179 stage.update(planeV.data(), sizeY+sizeU, (f.height()/2)*planeV.stride());
180
181 {
182 auto cmd = this->cmd[fId].startEncoding(device);
183 cmd.setFramebuffer({{frameImg, Vec4(0), Tempest::Preserve}});
184
185 struct Push { uint32_t strideY; uint32_t strideU; uint32_t strideV; } push = {};
186 push.strideY = planeY.stride();
187 push.strideU = planeU.stride();
188 push.strideV = planeV.stride();
189
190 cmd.setDebugMarker("Bink");
191 cmd.setPushData(push);
192 cmd.setBinding(0, stage, 0);
193 cmd.setBinding(1, stage, sizeY);
194 cmd.setBinding(2, stage, sizeY + sizeU);
195 cmd.setPipeline(Shaders::inst().bink);
196 cmd.draw(nullptr, 0, 3);
197 }
198 sync[fId] = device.submit(this->cmd[fId]);
199 }
200
201 [[deprecated]]
202 void yuvToRgba(const Bink::Frame& f, Pixmap& pm) {
203 if(pm.w()!=f.width() || pm.h()!=f.height())
204 pm = Pixmap(f.width(),f.height(),TextureFormat::RGBA8);
205 auto& planeY = f.plane(0);
206 auto& planeU = f.plane(1);
207 auto& planeV = f.plane(2);
208 auto dst = reinterpret_cast<uint8_t*>(pm.data());
209
210 const uint32_t w = pm.w();
211 for(uint32_t y=0; y<pm.h(); ++y)
212 for(uint32_t x=0; x<w; ++x) {
213 uint8_t* rgb = &dst[(x+y*w)*4];
214 float Y = planeY.at(x, y );
215 float U = planeU.at(x/2,y/2);
216 float V = planeV.at(x/2,y/2);
217
218 float r = 1.164f * (Y - 16.f) + 1.596f * (V - 128.f);
219 float g = 1.164f * (Y - 16.f) - 0.813f * (V - 128.f) - 0.391f * (U - 128.f);
220 float b = 1.164f * (Y - 16.f) + 2.018f * (U - 128.f);
221
222 r = std::max(0.f,std::min(r,255.f));
223 g = std::max(0.f,std::min(g,255.f));
224 b = std::max(0.f,std::min(b,255.f));
225
226 rgb[0] = uint8_t(r);
227 rgb[1] = uint8_t(g);
228 rgb[2] = uint8_t(b);
229 rgb[3] = 255;
230 }
231 }
232
233 bool isEof() const {
234 return vid.currentFrame()>=vid.frameCount();
235 }
236
237 std::unique_ptr<zenkit::Read> fin;
240 uint64_t frameTime = 0;
241
242 Tempest::Attachment frameImg;
243 Tempest::CommandBuffer cmd [Resources::MaxFramesInFlight];
244 Tempest::StorageBuffer staging[Resources::MaxFramesInFlight];
246
247 Tempest::SoundDevice sndDev;
248 std::vector<std::unique_ptr<SoundContext>> sndCtx;
249 };
250
252 setCursorShape(CursorShape::Hidden);
253 }
254
257
258void VideoWidget::pushVideo(std::string_view filename) {
259 const int scaleVideos = Gothic::settingsGetI("GAME", "scaleVideos");
260 if(scaleVideos<0)
261 return;
262 std::lock_guard<std::mutex> guard(syncVideo);
263 pendingVideo.emplace(filename);
264 hasPendingVideo.store(true);
265 }
266
268 return ctx!=nullptr || hasPendingVideo.load();
269 }
270
272 if(ctx!=nullptr && ctx->isEof()) {
273 stopVideo();
274 }
275
276 if(ctx!=nullptr)
277 return;
278
279 if(!hasPendingVideo)
280 return;
281
282 std::string filename;
283 {
284 std::lock_guard<std::mutex> guard(syncVideo);
285 if(pendingVideo.empty())
286 return;
287 filename = std::move(pendingVideo.front());
288 pendingVideo.pop();
289 if(pendingVideo.size()==0)
290 hasPendingVideo.store(false);
291 }
292
293 std::unique_ptr<zenkit::Read> read;
294 if(auto* entry = Resources::vdfsIndex().find(filename)) {
295 read = entry->open_read();
296 }
297 else if(auto* entry = Resources::vdfsIndex().find(filename+".bik")) {
298 // some api-calls are missing extension
299 read = entry->open_read();
300 }
301 else {
302 Log::e("unable to locate video file: \"",filename,"\"");
303 stopVideo();
304 return;
305 }
306
307 try {
308 ctx.reset(new Context(std::move(read)));
309 if(!active) {
310 active = true;
311 restoreMusic = GameMusic::inst().isEnabled();
313 }
314 }
315 catch(...){
316 Log::e("unable to play video: \"",filename,"\"");
317 stopVideo();
318 }
319 }
320
321void VideoWidget::keyDownEvent(KeyEvent& event) {
322 if(event.key==Event::K_ESCAPE) {
323 stopVideo();
324 }
325 }
326
327void VideoWidget::keyUpEvent(KeyEvent&) {
328 }
329
330void VideoWidget::mouseDownEvent(Tempest::MouseEvent& event) {
331 if(!active) {
332 event.ignore();
333 return;
334 }
335#if defined(__MOBILE_PLATFORM__)
336 stopVideo();
337#endif
338 }
339
340void VideoWidget::stopVideo() {
341 ctx.reset();
342 if(!hasPendingVideo) {
343 if(restoreMusic && !GameMusic::inst().isEnabled())
345 active = false;
346 }
347 update();
348 }
349
350void VideoWidget::paint(Tempest::Device& device, uint8_t fId) {
351 if(ctx==nullptr)
352 return;
353 try {
354 ctx->advance(device, fId);
355 frame = &textureCast<Texture2d&>(ctx->frameImg);
356 update();
357 }
358 catch(const Bink::VideoDecodingException& e) { // video exception is recoverable
359 Log::e("video decoding error. frame: ",ctx->vid.currentFrame(),", what: \"", e.what(), "\"");
360 }
361 catch(...) {
362 Log::e("video decoding error. frame: ",ctx->vid.currentFrame());
363 ctx.reset();
364 }
365 }
366
367void VideoWidget::paintEvent(PaintEvent& e) {
368 if(ctx==nullptr || frame==nullptr)
369 return;
370 float k = float(w())/float(frame->w());
371 int vh = int(k*float(frame->h()));
372
373 Painter p(e);
374 p.setBrush(Color(0,0,0,1));
375 p.drawRect(0,0,w(),h());
376
377 p.setBrush(Brush(*frame,Painter::NoBlend,ClampMode::ClampToEdge));
378 p.drawRect(0,(h()-vh)/2,w(),vh,
379 0,0,p.brush().w(),p.brush().h());
380 }
const Plane & plane(uint8_t id) const
Definition frame.h:49
uint32_t height() const
Definition frame.h:47
uint32_t width() const
Definition frame.h:46
size_t currentFrame() const
Definition video.h:58
const Frame & nextFrame()
Definition video.cpp:928
const FrameRate & fps() const
Definition video.h:60
size_t frameCount() const
Definition video.cpp:943
size_t audioCount() const
Definition video.h:63
const Audio & audio(uint8_t i) const
Definition video.h:64
void setEnabled(bool e)
static GameMusic & inst()
bool isEnabled() const
static Gothic & inst()
Definition gothic.cpp:249
static int settingsGetI(std::string_view sec, std::string_view name)
Definition gothic.cpp:807
static float settingsGetF(std::string_view sec, std::string_view name)
Definition gothic.cpp:841
@ MaxFramesInFlight
Definition resources.h:48
static Tempest::Device & device()
Definition resources.h:83
static const zenkit::Vfs & vdfsIndex()
static void recycle(Tempest::DescriptorArray &&arr)
static Shaders & inst()
Definition shaders.cpp:39
Tempest::RenderPipeline bink
Definition shaders.h:40
void paintEvent(Tempest::PaintEvent &event) override
void keyUpEvent(Tempest::KeyEvent &event) override
void paint(Tempest::Device &device, uint8_t fId)
void keyDownEvent(Tempest::KeyEvent &event) override
bool isActive() const
void pushVideo(std::string_view filename)
void mouseDownEvent(Tempest::MouseEvent &event) override
uint64_t tickSound() const
Tempest::Attachment frameImg
Tempest::CommandBuffer cmd[Resources::MaxFramesInFlight]
std::unique_ptr< zenkit::Read > fin
Tempest::StorageBuffer staging[Resources::MaxFramesInFlight]
std::vector< std::unique_ptr< SoundContext > > sndCtx
Context(std::unique_ptr< zenkit::Read > &&f)
void advance(Tempest::Device &device, uint8_t fId)
void yuvToRgba(const Bink::Frame &f, Pixmap &pm)
void yuvToRgba(const Bink::Frame &f, Tempest::Device &device, uint8_t fId)
Tempest::SoundDevice sndDev
Tempest::Fence sync[Resources::MaxFramesInFlight]
void read(void *dest, size_t count) override
void seek(size_t pos) override
Input(zenkit::Read &fin)
void skip(size_t count) override
zenkit::Read & fin
Tempest::SoundEffect snd
void pushSamples(const std::vector< float > &s)
uint64_t tickCount() const
SoundContext(Context &ctx, SoundDevice &dev, uint16_t sampleRate, bool isMono)
std::vector< float > samples
SoundContext & ctx
void renderSound(int16_t *out, size_t n) override
Sound(SoundContext &c, uint16_t sampleRate, bool isMono)