diff --git a/Makefile b/Makefile index d1b24c9..0443115 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,11 @@ CXX=g++ CXXOPTS=-Og -ggdb -fvar-tracking-assignments -Wall -Wextra -Wno-unused-function -pedantic -std=c++14 -march=native -fstack-protector-strong --param=ssp-buffer-size=4 -flto -I. LDOPTS=-Wl,--sort-common,--as-needed -render_CXXSRCS ::= render/Renderer.cc render/GlResource.cc render/ProgramProvider.cc render/renderutil.cc render/Overlay.cc render/VBOManager.cc render/Object.cc +render_CXXSRCS ::= render/Renderer.cc render/GlResource.cc render/ProgramProvider.cc render/renderutil.cc render/Overlay.cc render/VBOManager.cc render/Object.cc render/AlResource.cc render/AudioStream.cc +render_LIBS ::= -lSDL2 -lSDL2_ttf -lglbinding -lopenal game_CXXSRCS ::= game/GSMvePlay.cc game/GSShowObject.cc + iffexplore_CXXSRCS ::= iffexplore.cc IffFile.cc util.cc exceptions.cc iffexplore_LIBS ::= @@ -15,10 +17,10 @@ font2png_CXXSRCS ::= font2png.cc font2png_LIBS ::= -lpng mvedecode_CXXSRCS ::= mvedecode.cc TreFile.cc IffFile.cc util.cc MveDecoder.cc exceptions.cc decompress.cc $(render_CXXSRCS) $(game_CXXSRCS) -mvedecode_LIBS ::= -lSDL2 -lSDL2_ttf -lglbinding +mvedecode_LIBS ::= $(render_LIBS) objdecode_CXXSRCS ::= objdecode.cc TreFile.cc IffFile.cc util.cc ObjDecoder.cc exceptions.cc decompress.cc PaletteDecoder.cc $(render_CXXSRCS) $(game_CXXSRCS) -objdecode_LIBS ::= -lSDL2 -lSDL2_ttf -lglbinding +objdecode_LIBS ::= $(render_LIBS) progs ::= iffexplore treexplore mvedecode objdecode diff --git a/MveDecoder.cc b/MveDecoder.cc index db0be9f..a28b944 100644 --- a/MveDecoder.cc +++ b/MveDecoder.cc @@ -2,6 +2,7 @@ #include #include "common.hh" +#include "compiler.hh" #include "util.hh" #include "IffFile.hh" #include "decompress.hh" @@ -130,15 +131,17 @@ MveDecoder::MveDecoder(uint8_t const* base, size_t length) } } -std::vector parseHuff_(uint8_t const* data, size_t len) +template +size_t parseHuff_(uint8_t const* data, size_t len, OutputIt commands, size_t maxOut) { - uint8_t numHuffVals; - memcpy(&numHuffVals, data, 1); + const uint8_t numHuffVals = *data; if (numHuffVals != 22) throw FormatException{"Unexpected huffman tree size"}; std::array huffTree; - memcpy(huffTree.data(), data+1, 44); + std::copy(data+1, data+45, + huffTree.begin()); + // memcpy(huffTree.data(), data+1, 44); #ifdef HUFFDEBUG_ printf("Coded data length: %d\nTree:\n", segOfs[1]-(segOfs[0]+45)); @@ -157,8 +160,8 @@ std::vector parseHuff_(uint8_t const* data, size_t len) int byteValid = 0; uint8_t byteBuf = 0; unsigned bytePos = 45; - std::vector commands; unsigned huffIdx = huffTree.size(); + size_t count = 0; while (huffIdx != 22) { // Read next bit unsigned bit; @@ -179,31 +182,28 @@ std::vector parseHuff_(uint8_t const* data, size_t len) if (huffIdx < 22) { - commands.push_back(huffIdx); + *commands++ = huffIdx; + if (++count >= maxOut) + throw Exception{"Output buffer overflow"}; huffIdx = huffTree.size(); } } - if (commands.empty()) + if (count == 0) throw FormatException{"No commands decoded"}; - -#ifdef HUFFDEBUG_ - for(auto ent : commands) - printf("%.2hhx ", ent); - printf("\n"); -#endif - return commands; + return count; } -std::vector parsePixels_(uint8_t const* data, size_t len) +size_t parsePixels_(uint8_t const* RESTRICT data, size_t len, uint8_t * RESTRICT out, size_t maxOut) { if (*data == 0x02) { - return decompressLZ(data+1, len-1); + return decompressLZInto(data+1, len-1, out, maxOut); } else { - std::vector ret; - std::copy(data+1, data+len, std::back_inserter(ret)); - return ret; + if (len-1 > maxOut) + throw Exception{"Output buffer overflow"}; + std::copy(data+1, data+len, out); + return len-1; } } @@ -221,8 +221,19 @@ MveDecoder::Movie::Movie(std::vector::const_iterator shotIt, : shotIt_(shotIt), shotEnd_(shotEnd), frameIt_(shotIt_->VGAs.cbegin()), audioIt_(shotIt_->AUDIs.cbegin()), - mve_(mve) + mve_(mve), maxPixel_(0), maxCommand_(0) { + pixelBuf_.resize(mve_.width_*mve_.height_); + commandBuf_.reserve(32768); + frameBuf_.reserve(mve_.width_*mve_.height_); + frame_.reserve(mve_.width_*mve_.height_); +} + +MveDecoder::Movie::~Movie() +{ +#ifndef NDEBUG + printf("Maximum command buffer: %zu\n", maxCommand_); +#endif } bool MveDecoder::Movie::hasNext() const { @@ -234,7 +245,7 @@ bool MveDecoder::Movie::hasNext() const { std::tuple MveDecoder::Movie::decodeNext() { - frame_ = mve_.parseVGA(**frameIt_++, &frame_); + parseVGA(**frameIt_++); auto& palette = shotIt_->palt; if (frameIt_ == shotIt_->VGAs.cend()) { @@ -256,12 +267,12 @@ bool MveDecoder::Movie::hasAudio() const MveDecoder::Audio MveDecoder::Movie::getNextAudio() { MveDecoder::Audio ret; - if (audioIt_ != shotIt_->AUDIs.cend()) { + if ((shotIt_ != shotEnd_) && (audioIt_ != shotIt_->AUDIs.cend())) { ret.reserve((shotIt_->AUDIs.cend()-audioIt_)*2940); for(;audioIt_ != shotIt_->AUDIs.cend();++audioIt_) - std::copy((*audioIt_)->begin(), (*audioIt_)->end(), - std::back_inserter(ret)); + for(auto it = (*audioIt_)->begin();it != (*audioIt_)->end();it += 2) + ret.push_back(readU16LE(it)); } return ret; @@ -277,7 +288,7 @@ unsigned MveDecoder::Movie::getHeight() const return mve_.height_; } -MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* prev) const +void MveDecoder::Movie::parseVGA(IffFile::Object const& vga) { // Parse header if (vga.getSize() < 8) @@ -298,11 +309,17 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* (vga.getSize() - segOfs[0] < 45)) throw FormatException{"Segment 1 too small"}; - auto commands = parseHuff_(vga.begin()+segOfs[0], segOfs[1]-segOfs[0]); + commandBuf_.clear(); + parseHuff_(vga.begin()+segOfs[0], segOfs[1]-segOfs[0], + std::back_inserter(commandBuf_), commandBuf_.capacity()); + maxCommand_ = std::max(maxCommand_, commandBuf_.size()); - std::vector pixels; - if (segOfs[3]) - pixels = parsePixels_(vga.begin()+segOfs[3], vga.getSize()-segOfs[3]); + size_t pixelCount = 0; + if (segOfs[3]) { + pixelCount = parsePixels_(vga.begin()+segOfs[3], vga.getSize()-segOfs[3], + pixelBuf_.data(), pixelBuf_.size()); + maxPixel_ = std::max(maxPixel_, pixelCount); + } #ifdef VIDDEBUG_ printf("%lu commands, %lu pixel data, %u mvecs, %lu size data\n", @@ -311,17 +328,13 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* #endif // Interpret command stream to render frame - Frame ret; - ret.reserve(width_*height_); - + frameBuf_.clear(); bool flag = false; size_t seg2Idx = 0; size_t seg3Idx = 0; size_t seg4Idx = 0; unsigned size = 0; - auto it = commands.begin(); - while (it != commands.end()) { - auto cmd = *it++; + for (auto cmd : commandBuf_) { #ifdef CMDDEBUG_ printf("CMD %u ", cmd); #endif @@ -388,24 +401,22 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* #ifdef CMDDEBUG_ printf("PrevCopy: %u\n", size); #endif - if (!prev) - throw FormatException{"Reference to non-existing prev. frame"}; - if (ret.size()+size > prev->size()) + if (frameBuf_.size()+size > frame_.size()) throw FormatException{"Copy from prev exceeds frame"}; - std::copy(prev->begin()+ret.size(), - prev->begin()+ret.size()+size, - std::back_inserter(ret)); + std::copy(frame_.begin()+frameBuf_.size(), + frame_.begin()+frameBuf_.size()+size, + std::back_inserter(frameBuf_)); } else { #ifdef CMDDEBUG_ printf("DataCopy: %u @%lu\n", size, seg4Idx); #endif - if (seg4Idx+size > pixels.size()) + if (seg4Idx+size > pixelCount) throw FormatException{"Copy from seg4 exceeds bounds"}; - std::copy(pixels.begin()+seg4Idx, - pixels.begin()+seg4Idx+size, - std::back_inserter(ret)); + std::copy(pixelBuf_.begin()+seg4Idx, + pixelBuf_.begin()+seg4Idx+size, + std::back_inserter(frameBuf_)); seg4Idx += size; } } else { @@ -416,39 +427,37 @@ MveDecoder::Frame MveDecoder::parseVGA(IffFile::Object const& vga, Frame const* (segOfs[3] && (segOfs[2]+seg3Idx > segOfs[3])) || (segOfs[2]+seg3Idx > vga.getSize())) throw FormatException{"Segment 3 overrun"}; - if (!prev) - throw FormatException{"Reference to non-existing prev. frame"}; - + const uint8_t mpos = vga.begin()[segOfs[2]+seg3Idx++]; const signed x = sextend(mpos>>4, 4), y = sextend(mpos&0xf,4); - const signed delta = y*width_+x; - if ((delta < 0) && (ret.size() < static_cast(-delta))) + const signed delta = y*mve_.width_+x; + if ((delta < 0) && (frameBuf_.size() < static_cast(-delta))) throw FormatException{"Motion vector outside frame"}; - const unsigned ref = ret.size()+delta; + const unsigned ref = frameBuf_.size()+delta; #ifdef CMDDEBUG_ printf("@%lu %.2hhx -> (%d, %d) -> d %d -> %u\n", seg3Idx-1, mpos, x, y, delta, ref); #endif - if (ref+size > prev->size()) + if (ref+size > frame_.size()) throw FormatException{"Copy from prev exceeds frame"}; - std::copy(prev->begin()+ref, - prev->begin()+ref+size, - std::back_inserter(ret)); + std::copy(frame_.begin()+ref, + frame_.begin()+ref+size, + std::back_inserter(frameBuf_)); flag = false; } } - if (ret.size() != width_*height_) + if (frameBuf_.size() != mve_.width_*mve_.height_) throw FormatException{"Decoded frame dimension mismatch"}; #ifndef NDEBUG - if (seg4Idx < pixels.size()) - printf("%lu unused pixel data\n", pixels.size()-seg4Idx); + if (seg4Idx < pixelCount) + printf("%lu unused pixel data\n", pixelCount-seg4Idx); #endif - return ret; + frame_.swap(frameBuf_); } /* #include diff --git a/MveDecoder.hh b/MveDecoder.hh index 72cad72..286d422 100644 --- a/MveDecoder.hh +++ b/MveDecoder.hh @@ -15,7 +15,7 @@ public: } using Frame = std::vector; - using Audio = std::vector; + using Audio = std::vector; using Palette = std::array; private: using AUDIVec = std::vector; @@ -32,7 +32,8 @@ public: public: bool hasNext() const; std::tuple decodeNext(); - + + ~Movie(); bool hasAudio() const; Audio getNextAudio(); @@ -44,12 +45,23 @@ public: Movie(std::vector::const_iterator shotIt, std::vector::const_iterator shotEnd, MveDecoder const& mve); - + + void parseVGA(IffFile::Object const& vga); + std::vector::const_iterator shotIt_, shotEnd_; VGAVec::const_iterator frameIt_; AUDIVec::const_iterator audioIt_; Frame frame_; MveDecoder const& mve_; + + // Buffers for frame decoding + std::vector pixelBuf_; + Frame frameBuf_; + std::vector commandBuf_; + + // Buffer usage stats + size_t maxPixel_, maxCommand_; + friend class MveDecoder; }; @@ -63,8 +75,6 @@ private: std::vector palts_; std::vector > branches_; - Frame parseVGA(IffFile::Object const& vga, Frame const* prev) const; - friend struct AudioCBData; }; diff --git a/TreFile.cc b/TreFile.cc index efe93c7..63b94a4 100644 --- a/TreFile.cc +++ b/TreFile.cc @@ -171,8 +171,8 @@ TreFile::TreFile(uint8_t const* base, size_t length) crc, table3Ptr); continue; } - printf("Table1->Table2 pointer resolved: Table 2 %.8x -> Table 3 %.8x\n", - table3Ptr, it->second); + // printf("Table1->Table2 pointer resolved: Table 2 %.8x -> Table 3 %.8x\n", + // table3Ptr, it->second); table3Ptr = it->second; } diff --git a/compiler.hh b/compiler.hh new file mode 100644 index 0000000..a8a20d3 --- /dev/null +++ b/compiler.hh @@ -0,0 +1,14 @@ +#ifndef WC3RE_COMPILER_HH__ +#define WC3RE_COMPILER_HH__ + +#ifdef __GNUG__ +#define RESTRICT __restrict__ +#else +#ifdef __MSC_VER +#define RESTRICT __restrict +#else +#define RESTRICT +#endif +#endif + +#endif diff --git a/decompress.cc b/decompress.cc index 3ada568..284849e 100644 --- a/decompress.cc +++ b/decompress.cc @@ -1,4 +1,5 @@ #include "common.hh" +#include "compiler.hh" #include "decompress.hh" @@ -80,3 +81,92 @@ std::vector decompressLZ(uint8_t const* data, size_t len) } return ret; } + +size_t decompressLZInto(uint8_t const* RESTRICT data, size_t len, uint8_t * RESTRICT out, size_t maxOut) +{ + size_t pos = 0, outPos = 0; + while (pos < len) { + uint8_t b = *(data+pos++); + if (!((b&0xe0)==0xe0)) { + unsigned size = 0, replSize = 0; + unsigned replOfs = 0; + if (!(b&0x80)) { + if (pos >= len) + throw FormatException{"Compressed stream overrun"}; + + uint8_t ofs = *(data+pos++); + size = b&0x3; + + replSize = ((b&0x1c)>>2) + 3; + replOfs = ((b&0x60)<<3)+ofs+1; + } else if (!(b&0x40)) { + if (pos+1 >= len) + throw FormatException{"Compressed stream overrun"}; + + uint8_t b1 = *(data+pos++); + uint8_t b2 = *(data+pos++); + + size = (b1&0xc0)>>6; + + replSize = (b&0x3f)+4; + replOfs = ((b1&0x3f)<<8)+b2+1; + } else if (!(b&0x20)) { + if (pos+2 >= len) + throw FormatException{"Compressed stream overrun"}; + + uint8_t b1 = *(data+pos++); + uint8_t b2 = *(data+pos++); + uint8_t b3 = *(data+pos++); + + size = b&0x3; + + replSize = b3+5+((b&0xc)<<6); + replOfs = ((b&0x10)<<12)+1+(b1<<8)+b2; + } + if (pos+size >= len) + throw FormatException{"Compressed stream overrun"}; + if (outPos+size > maxOut) + throw Exception{"Output buffer overrun"}; + std::copy(data+pos, data+pos+size, + out+outPos); + pos += size; + outPos += size; + + if (replOfs > outPos) + throw FormatException{"Replication offset exceeds buffer"}; + if (outPos+replSize > maxOut) + throw Exception{"Output buffer overrun"}; + unsigned start = outPos-replOfs; + for (unsigned i = 0;i < replSize;++i) + out[outPos++] = out[start+i]; + } else { + unsigned size = (b&0x1f)*4+4; + if (size > 0x70) { + if (pos+(b&0x3) > len) + throw FormatException{"Compressed stream overrun"}; + if (outPos+(b&0x03) > maxOut) + throw Exception{"Output buffer overrun"}; + std::copy(data+pos, data+pos+(b&0x3), + out+outPos); + pos += (b&0x3); + outPos += (b&0x3); +#ifndef NDEBUG + if (pos < len) + printf("%lu unparsed bytes in compressed data\n", len-pos); +#endif + break; + } + + if (pos+size > len) + throw FormatException{"Compressed stream overrun"}; + if (outPos+size > maxOut) + throw Exception{"Output buffer overrun"}; + std::copy(data+pos, data+pos+size, + out+outPos); + pos += size; + outPos += size; + } + } + + return outPos; +} diff --git a/decompress.hh b/decompress.hh index 3321ed1..fd64b17 100644 --- a/decompress.hh +++ b/decompress.hh @@ -9,6 +9,11 @@ /* LZ77-like format consisting of codes specifing copies from the input stream and/or replication of previously output data */ + +/* Decompress compressed data in 'data', return decompressed data */ std::vector decompressLZ(uint8_t const* data, size_t len); +/* Decompress compressed data in 'data' into pre-allocated buffer 'out' of size 'maxOut', + return size of decompressed data */ +size_t decompressLZInto(uint8_t const* data, size_t len, uint8_t * out, size_t maxOut); #endif diff --git a/game/GSMvePlay.cc b/game/GSMvePlay.cc index 5887101..424c0ad 100644 --- a/game/GSMvePlay.cc +++ b/game/GSMvePlay.cc @@ -3,12 +3,13 @@ #include "GSMvePlay.hh" #include "render/Overlay.hh" #include "render/Renderer.hh" +#include "render/AudioStream.hh" namespace game { GSMvePlay::GSMvePlay(render::Renderer& renderer, MveDecoder::Movie& movie) : GameState(renderer), movie_(movie), overlay_(nullptr), delta_(0), - nextFT_(1000/15.0f), frameRGB_() + nextFT_(1000/15.0f), frameRGBA_(), decoded_(false), firstFrame_(true) { int scrWidth = renderer_.getWidth(), scrHeight = renderer_.getHeight(); const float mvePAR = 1.2f; @@ -30,12 +31,13 @@ namespace game { overlay_ = std::make_unique(renderer_, scrWidth, scrHeight, left, top, mveWidth, mveHeight); + + audio_ = std::make_unique(); - auto align4Width = (mveWidth%4)?(mveWidth+(4-(mveWidth%4))):mveWidth; - frameRGB_.resize(align4Width*mveHeight*3); - + frameRGBA_.resize(mveWidth*mveHeight*4); + decode_(); - overlay_->setContentRGB8(frameRGB_.data()); + overlay_->setContentBGRA8(frameRGBA_.data()); decode_(); } @@ -45,6 +47,10 @@ namespace game { void GSMvePlay::decode_() { + if (movie_.hasAudio()) { + auto samples = movie_.getNextAudio(); + audio_->queueSamples(1, 22050, samples.data(), samples.size()*2); + } if (movie_.hasNext()) { auto dec = movie_.decodeNext(); auto& frame = std::get<0>(dec); @@ -55,29 +61,46 @@ namespace game { for (unsigned y = 0;y < height;++y) { for (unsigned x = 0;x < width;++x) { auto idx = frame[y*width+x]; - std::copy(&palette[idx*3], &palette[idx*3+3], - &frameRGB_[i]); - i += 3; + + frameRGBA_[i] = palette[idx*3+2]; + frameRGBA_[i+1] = palette[idx*3+1]; + frameRGBA_[i+2] = palette[idx*3]; + frameRGBA_[i+3] = 255u; + + i += 4; } - if ((i%4) != 0) - i += 4-(i%4); } } else - frameRGB_.resize(0); + frameRGBA_.resize(0); } bool GSMvePlay::handleEvent(SDL_Event& event) { + switch (event.type) { + case SDL_KEYDOWN: + if (event.key.keysym.sym == SDLK_ESCAPE) { + renderer_.popGS(); + return true; + } + break; + } + return false; } void GSMvePlay::draw(unsigned delta_ms) { const float frameTime = 1000/15.0f; + if (firstFrame_) { + firstFrame_ = false; + audio_->play(); + } delta_ += delta_ms; if (delta_ >= nextFT_) { - if (!frameRGB_.size()) { + if (!decoded_) + decode_(); + if (!frameRGBA_.size()) { renderer_.popGS(); return; } @@ -89,11 +112,14 @@ namespace game { delta_ = 0; - overlay_->setContentRGB8(frameRGB_.data()); + overlay_->setContentBGRA8(frameRGBA_.data()); - // Decode next frame + decoded_ = false; + } else if (!decoded_) { decode_(); + decoded_ = true; } + overlay_->draw(); } } diff --git a/game/GSMvePlay.hh b/game/GSMvePlay.hh index e291969..59f323f 100644 --- a/game/GSMvePlay.hh +++ b/game/GSMvePlay.hh @@ -6,6 +6,7 @@ class MveDecoder; namespace render { class Overlay; + class AudioStream; } namespace game { @@ -20,9 +21,11 @@ namespace game { private: MveDecoder::Movie& movie_; std::unique_ptr overlay_; + std::unique_ptr audio_; unsigned delta_; float nextFT_; - std::vector frameRGB_; + std::vector frameRGBA_; + bool decoded_, firstFrame_; void decode_(); }; diff --git a/render/AlResource.cc b/render/AlResource.cc new file mode 100644 index 0000000..000ca8b --- /dev/null +++ b/render/AlResource.cc @@ -0,0 +1,33 @@ +#include "AlResource.hh" + +namespace render { + void ALCDeviceDeleter::operator() (ALCdevice *dev) const + { + alcCloseDevice(dev); + } + + void ALCContextDeleter::operator() (ALCcontext *ctx) const + { + alcDestroyContext(ctx); + } + + void ALSourceDeleter::operator() (ALuint src) const + { + alDeleteSources(1, &src); + } + + void ALSourceDeleter::operator() (ALsizei count, ALuint src[]) const + { + alDeleteSources(count, src); + } + + void ALBufferDeleter::operator() (ALuint buf) const + { + alDeleteBuffers(1, &buf); + } + + void ALBufferDeleter::operator() (ALsizei count, ALuint buf[]) const + { + alDeleteBuffers(count, buf); + } +} diff --git a/render/AlResource.hh b/render/AlResource.hh new file mode 100644 index 0000000..eca4fdb --- /dev/null +++ b/render/AlResource.hh @@ -0,0 +1,39 @@ +#ifndef WC3RE_RENDER_ALRESOURCE_HH__ +#define WC3RE_RENDER_ALRESOURCE_HH__ + +#include +#include +#include + +#include "GlResource.hh" + +namespace render { + struct ALCDeviceDeleter { + void operator() (ALCdevice *dev) const; + }; + + struct ALCContextDeleter { + void operator() (ALCcontext *ctx) const; + }; + + struct ALSourceDeleter { + void operator() (ALuint src) const; + void operator() (ALsizei count, ALuint src[]) const; + }; + + struct ALBufferDeleter { + void operator() (ALuint buf) const; + void operator() (ALsizei count, ALuint buf[]) const; + }; + + using ALCDeviceUPtr = std::unique_ptr; + using ALCContextUPtr = std::unique_ptr; + + template + using AlResource = GlResource; + + using ALSourceResource = AlResource; + using ALBufferResource = AlResource; +} + +#endif diff --git a/render/AudioStream.cc b/render/AudioStream.cc new file mode 100644 index 0000000..7cb86e8 --- /dev/null +++ b/render/AudioStream.cc @@ -0,0 +1,62 @@ +#include "AudioStream.hh" +#include "exceptions.hh" + +namespace render { + AudioStream::AudioStream() + { + alGenSources(1, &alSource_.get()); + + alSourcef(alSource_, AL_MIN_GAIN, 1.0f); + } + + AudioStream::~AudioStream() + { +#ifndef NDEBUG + printf("%zu buffers at destruction\n", alBufs_.size()); +#endif + } + + void AudioStream::play() + { + alSourcePlay(alSource_); + } + + void AudioStream::pause() + { + alSourceStop(alSource_); + } + + bool AudioStream::isPlaying() const + { + ALenum state; + alGetSourcei(alSource_, AL_SOURCE_STATE, &state); + return (state == AL_PLAYING); + } + void AudioStream::queueSamples(unsigned channels, unsigned freq, int16_t const* data, size_t len) + { + // Reuse a processed buffer if possible, or create a new one + ALint doneBufs; + alGetSourcei(alSource_, AL_BUFFERS_PROCESSED, &doneBufs); + ALuint buf; + if (doneBufs > 0) + alSourceUnqueueBuffers(alSource_, 1, &buf); + else { + alGenBuffers(1, &buf); + alBufs_.push_back(buf); + } + + // Fill buffer with data + ALenum format; + if (channels == 1) + format = AL_FORMAT_MONO16; + else if (channels == 2) + format = AL_FORMAT_STEREO16; + else + throw Exception{"More than 2 channels not supported"}; + + alBufferData(buf, format, data, len, freq); + + // Enqueue buffer + alSourceQueueBuffers(alSource_, 1, &buf); + } +} diff --git a/render/AudioStream.hh b/render/AudioStream.hh new file mode 100644 index 0000000..49ef78b --- /dev/null +++ b/render/AudioStream.hh @@ -0,0 +1,26 @@ +#ifndef WC3RE_RENDER_AUDIOSTREAM_HH__ +#define WC3RE_RENDER_AUDIOSTREAM_HH__ + +#include + +#include "AlResource.hh" + +namespace render { + class AudioStream { + public: + AudioStream(); + ~AudioStream(); + + void play(); + void pause(); + bool isPlaying() const; + + void queueSamples(unsigned channels, unsigned freq, int16_t const* data, size_t len); + + private: + ALSourceResource alSource_; + std::vector alBufs_; + }; +} + +#endif diff --git a/render/Overlay.cc b/render/Overlay.cc index 8ceb56c..7efff18 100644 --- a/render/Overlay.cc +++ b/render/Overlay.cc @@ -71,15 +71,15 @@ namespace render { glDrawArrays(GL_TRIANGLES, 0, 6); } - void Overlay::clear() - { - if (SDL_GL_ExtensionSupported("GL_ARB_clear_texture")) - glClearTexImage(texture_, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr); - else { - std::vector zeros(intWidth_*intHeight_*4, 0u); - setContentBGRA8(zeros.data()); - } - } + // void Overlay::clear() + // { + // if (SDL_GL_ExtensionSupported("GL_ARB_clear_texture")) + // glClearTexImage(texture_, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr); + // else { + // std::vector zeros(intWidth_*intHeight_*4, 0u); + // setContentBGRA8(zeros.data()); + // } + // } void Overlay::setContent(SDL_Surface *content) { @@ -91,6 +91,7 @@ namespace render { glBindTexture(GL_TEXTURE_2D, texture_); if (content->format->format != SDL_PIXELFORMAT_ARGB8888) { + printf("Warning: Format conversion in Overlay::setContent\n"); SDLSurfaceUPtr tmpSurf(SDL_ConvertSurfaceFormat(content, SDL_PIXELFORMAT_ARGB8888, 0)); if (!tmpSurf) throw SDLException{}; diff --git a/render/Overlay.hh b/render/Overlay.hh index 3cd37d0..260836b 100644 --- a/render/Overlay.hh +++ b/render/Overlay.hh @@ -18,7 +18,7 @@ namespace render { void draw() override; - void clear(); + // void clear(); void setContent(SDL_Surface *content); void setContentRGB8(void *data); diff --git a/render/Renderer.cc b/render/Renderer.cc index f886dec..42341c5 100644 --- a/render/Renderer.cc +++ b/render/Renderer.cc @@ -61,6 +61,7 @@ namespace render { Renderer::Renderer() : sdlInit_(), ttfInit_(), window_(), context_() { + // Initialize OpenGL glbinding::Binding::initialize(); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); @@ -98,6 +99,21 @@ namespace render { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glViewport(0, 0, width_, height_); + + // Initialize OpenAL + alcDevice_.reset(alcOpenDevice(nullptr)); + if (!alcDevice_) + throw Exception{"OpenAL init failed"}; + alcContext_.reset(alcCreateContext(alcDevice_.get(), nullptr)); + if (!alcContext_) + throw Exception{"OpenAL init failed"}; + alcMakeContextCurrent(alcContext_.get()); + + alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f); + alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alListener3f(AL_ORIENTATION, 0.0f, 0.0f, -1.0f); + + // Setup framerate display fpsFont_.reset(TTF_OpenFont("DejaVuSans.ttf", 10)); if (!fpsFont_) throw TTFException{}; @@ -110,10 +126,26 @@ namespace render { SDLSurfaceUPtr fpsSurf(TTF_RenderUTF8_Blended(fpsFont_.get(), "0 FPS", white)); if (!fpsSurf) throw TTFException{}; + + const unsigned fpsWidth = fpsSurf->w*2; + const unsigned fpsHeight = fpsSurf->h; + fpsOverlay_ = std::make_unique(*this, fpsWidth, fpsHeight, 0, 0, fpsWidth, fpsHeight); + { + int bpp; + uint32_t rmask, gmask, bmask, amask; + if (!SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ARGB8888, + &bpp, + &rmask, &gmask, &bmask, &amask)) + throw SDLException{}; + + fpsOverlaySurf_.reset(SDL_CreateRGBSurface(0, fpsWidth, fpsHeight, bpp, + rmask, gmask, bmask, amask)); + } - fpsOverlay_ = std::make_unique(*this, fpsSurf->w*2, fpsSurf->h, 0, 0, fpsSurf->w*2, fpsSurf->h); - fpsOverlay_->clear(); - fpsOverlay_->setContent(fpsSurf.get()); + if (SDL_BlitSurface(fpsSurf.get(), nullptr, fpsOverlaySurf_.get(), nullptr) != 0) + throw SDLException{}; + + fpsOverlay_->setContent(fpsOverlaySurf_.get()); } Renderer::~Renderer() @@ -129,7 +161,8 @@ namespace render { while (!close && gamestates_.size()) { SDL_Event event; - while (SDL_PollEvent(&event)) { + bool changeGS = false; + while (SDL_PollEvent(&event) && !changeGS) { switch(event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_q) { @@ -144,10 +177,10 @@ namespace render { } break; } - if (gamestates_.back()->handleEvent(event)) - continue; - break; + changeGS = gamestates_.back()->handleEvent(event); } + if (changeGS) + continue; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -159,12 +192,18 @@ namespace render { if (fpsTime+delta > 1000) { if (fpsCount+1 != lastFps) { std::string fpsText{std::to_string(fpsCount+1) + " FPS"}; + SDLSurfaceUPtr fpsSurf(TTF_RenderUTF8_Blended(fpsFont_.get(), fpsText.c_str(), SDL_Color({255, 255, 255, 255}))); if (!fpsSurf) throw TTFException{}; - fpsOverlay_->clear(); - fpsOverlay_->setContent(fpsSurf.get()); + + if ((SDL_FillRect(fpsOverlaySurf_.get(), nullptr, 0x00000000u) != 0) || + (SDL_BlitSurface(fpsSurf.get(), nullptr, fpsOverlaySurf_.get(), nullptr) != 0)) + throw SDLException{}; + + fpsOverlay_->setContent(fpsOverlaySurf_.get()); + lastFps = fpsCount+1; } fpsCount = 0; diff --git a/render/Renderer.hh b/render/Renderer.hh index 31f78fa..cbef52f 100644 --- a/render/Renderer.hh +++ b/render/Renderer.hh @@ -4,6 +4,7 @@ #include #include "sdlutil.hh" +#include "AlResource.hh" namespace game { class GameState; @@ -31,8 +32,14 @@ namespace render { SDLGLContext context_; std::vector > gamestates_; int width_, height_; + + ALCDeviceUPtr alcDevice_; + ALCContextUPtr alcContext_; + TTFFontUPtr fpsFont_; std::unique_ptr fpsOverlay_; + SDLSurfaceUPtr fpsOverlaySurf_; + }; } diff --git a/render/sdlutil.hh b/render/sdlutil.hh index 9f5a5db..3a27528 100644 --- a/render/sdlutil.hh +++ b/render/sdlutil.hh @@ -101,7 +101,7 @@ namespace render { // RAII wrapper for SDL_Init class SDLInit { public: - SDLInit(uint32_t flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO) { + SDLInit(uint32_t flags = SDL_INIT_VIDEO) { if (SDL_Init(flags) < 0) throw SDLException{}; }