diff --git a/.gitignore b/.gitignore index d8c263577..f6d23984b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ build distribute .vscodecounter +logs +distribute *.log diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 09db699dc..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "files.associations": { - "array": "cpp", - "atomic": "cpp", - "bit": "cpp", - "*.tcc": "cpp", - "cctype": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "list": "cpp", - "map": "cpp", - "set": "cpp", - "unordered_map": "cpp", - "vector": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "functional": "cpp", - "iterator": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "optional": "cpp", - "random": "cpp", - "string": "cpp", - "string_view": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "type_traits": "cpp", - "utility": "cpp", - "fstream": "cpp", - "initializer_list": "cpp", - "iosfwd": "cpp", - "istream": "cpp", - "limits": "cpp", - "new": "cpp", - "ostream": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "cinttypes": "cpp", - "typeinfo": "cpp", - "variant": "cpp", - "bitset": "cpp", - "chrono": "cpp", - "codecvt": "cpp", - "ctime": "cpp", - "ratio": "cpp", - "iomanip": "cpp", - "iostream": "cpp", - "compare": "cpp", - "concepts": "cpp", - "ranges": "cpp", - "numbers": "cpp", - "span": "cpp" - }, - "editor.fontSize": 14, - "editor.fontFamily": "'Cascadia Code', 'Courier New', monospace", - "editor.renderWhitespace": "all", - "editor.cursorStyle": "underline", - "files.insertFinalNewline": true, - "files.trimFinalNewlines": true, - "files.trimTrailingWhitespace": true, - "editor.formatOnSave": true, - "editor.bracketPairColorization.enabled": true, - "Lua.diagnostics.globals": ["love", "g_windowShown"] -} diff --git a/Makefile b/Makefile index 63899b1a5..cddf5c0b3 100644 --- a/Makefile +++ b/Makefile @@ -24,13 +24,14 @@ export LOVE_VERSION = 11.4.0 #----------------------------------- export LOVE_PORTLIBS = -lmodplug -lvorbisidec -lFLAC -lvorbisidec -logg LOVE_PORTLIBS += -lphysfs -llz4 -lz -lbox2d -ljpeg -lpng `curl-config --libs` +LOVE_PORTLIBS += -ltheora #------------------------------------ # Common configuration for consoles #------------------------------------ export APP_TITLE := LÖVE Potion export APP_AUTHOR := lövebrew team -export APP_VERSION := 2.3.2 +export APP_VERSION := 2.4.0 export APP_TITLEID := 1043 export DEFINES := -D__DEBUG__=$(DEBUG) -D__APP_VERSION__=\"$(APP_VERSION)\" \ diff --git a/include/common/delay.h b/include/common/delay.h new file mode 100644 index 000000000..74f5b568b --- /dev/null +++ b/include/common/delay.h @@ -0,0 +1,6 @@ +#pragma once + +namespace love +{ + void Sleep(float ms); +} diff --git a/include/common/module.h b/include/common/module.h index c13daa6a2..2e5cb0a41 100644 --- a/include/common/module.h +++ b/include/common/module.h @@ -31,6 +31,7 @@ namespace love M_TIMER, M_TOUCH, M_WINDOW, + M_VIDEO, M_MAX_ENUM }; diff --git a/include/common/pixelformat.h b/include/common/pixelformat.h index 2d51b4eda..ada3f9839 100644 --- a/include/common/pixelformat.h +++ b/include/common/pixelformat.h @@ -11,9 +11,18 @@ namespace love PIXELFORMAT_TEX3DS_RGBA8, // "regular" formats + PIXELFORMAT_RGB8, + PIXELFORMAT_RGBA8, PIXELFORMAT_RGBA16, + PIXELFORMAT_R8, + + PIXELFORMAT_RGBA4, + PIXELFORMAT_RGB565, + + PIXELFORMAT_LA8, + // depth/stencil PIXELFORMAT_STENCIL8, PIXELFORMAT_DEPTH16, diff --git a/include/modules/graphics/graphics.h b/include/modules/graphics/graphics.h index 6d6da836c..4804e4ebd 100644 --- a/include/modules/graphics/graphics.h +++ b/include/modules/graphics/graphics.h @@ -36,6 +36,9 @@ #include "objects/imagedata/imagedata.h" +#include "objects/video/video.h" +#include "objects/videostream/videostream.h" + #include "common/lmath.h" #include #include @@ -268,6 +271,9 @@ namespace love const Texture::Filter& filter = Texture::defaultFilter) = 0; #endif + Image* NewImage(Texture::TextureType t, PixelFormat format, int width, int height, + int slices); + Quad* NewQuad(Quad::Viewport v, double sw, double sh); Text* NewText(Font* font, const std::vector& text = {}); @@ -276,6 +282,8 @@ namespace love Font* GetFont(); + Video* NewVideo(VideoStream* stream, float dpiscale); + float GetPointSize() const; LineStyle GetLineStyle() const; diff --git a/include/modules/graphics/wrap_graphics.h b/include/modules/graphics/wrap_graphics.h index 732ef80dc..dafb05015 100644 --- a/include/modules/graphics/wrap_graphics.h +++ b/include/modules/graphics/wrap_graphics.h @@ -99,6 +99,8 @@ namespace Wrap_Graphics int NewCanvas(lua_State* L); + int NewVideo(lua_State* L); + int SetDefaultFilter(lua_State* L); int SetLineWidth(lua_State* L); diff --git a/include/modules/timer/timerc.h b/include/modules/timer/timerc.h index ebb418383..0380006bb 100644 --- a/include/modules/timer/timerc.h +++ b/include/modules/timer/timerc.h @@ -12,6 +12,8 @@ namespace love::common class Timer : public Module { public: + static constexpr auto SLEEP_DURATION = 1000000ULL; + Timer(); ModuleType GetModuleType() const diff --git a/include/modules/video/videomodule.h b/include/modules/video/videomodule.h new file mode 100644 index 000000000..df597e9a5 --- /dev/null +++ b/include/modules/video/videomodule.h @@ -0,0 +1,33 @@ +#pragma once + +#include "common/module.h" +#include "objects/file/file.h" +#include "objects/videostream/videostream.h" + +namespace love +{ + class Worker; + + class VideoModule : public Module + { + public: + VideoModule(); + + virtual ~VideoModule(); + + virtual const char* GetName() const + { + return "love.video"; + } + + virtual ModuleType GetModuleType() const + { + return M_VIDEO; + } + + VideoStream* NewVideoStream(File* file); + + private: + Worker* workerThread; + }; +} // namespace love diff --git a/include/modules/video/worker.h b/include/modules/video/worker.h new file mode 100644 index 000000000..2b2701398 --- /dev/null +++ b/include/modules/video/worker.h @@ -0,0 +1,34 @@ +#pragma once + +#include "modules/thread/types/conditional.h" +#include "modules/thread/types/threadable.h" + +#include "objects/videostream/theorastream.h" +#include "objects/videostream/utility/stream.h" + +#include + +namespace love +{ + class Worker : public Threadable + { + public: + Worker(); + + virtual ~Worker(); + + void ThreadFunction(); + + void AddStream(TheoraStream* stream); + + void Stop(); + + private: + std::vector> streams; + + thread::MutexRef mutex; + thread::ConditionalRef condition; + + bool stopping; + }; +} // namespace love diff --git a/include/modules/video/wrap_videomodule.h b/include/modules/video/wrap_videomodule.h new file mode 100644 index 000000000..d1e001bdb --- /dev/null +++ b/include/modules/video/wrap_videomodule.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common/luax.h" +#include "modules/video/videomodule.h" + +namespace Wrap_VideoModule +{ + int NewVideoStream(lua_State* L); + + int Register(lua_State* L); +} // namespace Wrap_VideoModule diff --git a/include/objects/image/image.h b/include/objects/image/image.h index 2072be64c..29bfa328e 100644 --- a/include/objects/image/image.h +++ b/include/objects/image/image.h @@ -51,9 +51,7 @@ namespace love std::vector>> data; }; -#if defined(__SWITCH__) void ReplacePixels(const void* data, size_t size, const Rect& rect); -#endif ~Image(); diff --git a/include/objects/video/videoc.h b/include/objects/video/videoc.h new file mode 100644 index 000000000..8a96bfd49 --- /dev/null +++ b/include/objects/video/videoc.h @@ -0,0 +1,60 @@ +#pragma once + +#include "objects/drawable/drawable.h" + +#include "objects/source/source.h" +#include "objects/videostream/videostream.h" + +#include "objects/image/image.h" +#include "objects/texture/texture.h" + +namespace love +{ + class Graphics; + + namespace common + { + class Video : public Drawable + { + public: + static love::Type type; + + Video(Graphics* graphics, VideoStream* stream, float dpiScale = 1.0f); + + virtual ~Video(); + + virtual void Draw(Graphics* graphics, const Matrix4& matrix) = 0; + + VideoStream* GetStream(); + + love::Source* GetSource(); + + void SetSource(love::Source* source); + + int GetWidth() const; + + int GetHeight() const; + + int GetPixelWidth() const; + + int GetPixelHeight() const; + + void SetFilter(const Texture::Filter& filter); + + const Texture::Filter& GetFilter() const; + + protected: + virtual void Update() = 0; + + StrongReference stream; + + int width; + int height; + + Texture::Filter filter; + + StrongReference source; + StrongReference images[3]; + }; + } // namespace common +} // namespace love diff --git a/include/objects/video/wrap_video.h b/include/objects/video/wrap_video.h new file mode 100644 index 000000000..c17eecec4 --- /dev/null +++ b/include/objects/video/wrap_video.h @@ -0,0 +1,33 @@ +#pragma once + +#include "common/luax.h" +#include "objects/video/video.h" + +namespace Wrap_Video +{ + int GetStream(lua_State* L); + + int GetSource(lua_State* L); + + int SetSource(lua_State* L); + + int GetWidth(lua_State* L); + + int GetHeight(lua_State* L); + + int GetDimensions(lua_State* L); + + int GetPixelWidth(lua_State* L); + + int GetPixelHeight(lua_State* L); + + int GetPixelDimensions(lua_State* L); + + int SetFilter(lua_State* L); + + int GetFilter(lua_State* L); + + love::Video* CheckVideo(lua_State* L, int index); + + int Register(lua_State* L); +} // namespace Wrap_Video diff --git a/include/objects/videostream/sync/deltasync.h b/include/objects/videostream/sync/deltasync.h new file mode 100644 index 000000000..50cb74cd5 --- /dev/null +++ b/include/objects/videostream/sync/deltasync.h @@ -0,0 +1,34 @@ +#pragma once + +#include "modules/thread/types/mutex.h" +#include "objects/videostream/sync/framesync.h" + +namespace love +{ + class DeltaSync : public FrameSync + { + public: + DeltaSync(); + + ~DeltaSync(); + + virtual double GetPosition() const override; + + virtual void Update(double dt) override; + + virtual void Play() override; + + virtual void Pause() override; + + virtual void Seek(double time) override; + + virtual bool IsPlaying() const override; + + private: + bool playing; + double position; + double speed; + + thread::MutexRef mutex; + }; +} // namespace love diff --git a/include/objects/videostream/sync/framesync.h b/include/objects/videostream/sync/framesync.h new file mode 100644 index 000000000..4ff27a16e --- /dev/null +++ b/include/objects/videostream/sync/framesync.h @@ -0,0 +1,32 @@ +#pragma once + +#include "objects/object.h" + +namespace love +{ + class FrameSync : public Object + { + public: + virtual ~FrameSync() + {} + + virtual double GetPosition() const = 0; + + virtual void Update(double) + {} + + void CopyState(const FrameSync* other); + + /* playback api */ + + virtual void Play() = 0; + + virtual void Pause() = 0; + + virtual void Seek(double offset) = 0; + + virtual double Tell() const; + + virtual bool IsPlaying() const = 0; + }; +} // namespace love diff --git a/include/objects/videostream/sync/sourcesync.h b/include/objects/videostream/sync/sourcesync.h new file mode 100644 index 000000000..e74554e89 --- /dev/null +++ b/include/objects/videostream/sync/sourcesync.h @@ -0,0 +1,28 @@ +#pragma once + +#include "common/strongref.h" + +#include "objects/source/source.h" +#include "objects/videostream/sync/framesync.h" + +namespace love +{ + class SourceSync : public FrameSync + { + public: + SourceSync(Source* source); + + virtual double GetPosition() const override; + + virtual void Play() override; + + virtual void Pause() override; + + virtual void Seek(double time) override; + + virtual bool IsPlaying() const override; + + private: + StrongReference source; + }; +} // namespace love diff --git a/include/objects/videostream/theora/oggdemuxer.h b/include/objects/videostream/theora/oggdemuxer.h new file mode 100644 index 000000000..70dcbdf13 --- /dev/null +++ b/include/objects/videostream/theora/oggdemuxer.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include "objects/file/file.h" + +#include + +namespace love +{ + class OggDemuxer + { + public: + OggDemuxer(File* file); + + ~OggDemuxer(); + + enum StreamType + { + TYPE_THEORA, + TYPE_UNKNOWN + }; + + static constexpr int SYNC_VALUE = 0x2000; + static constexpr int THEORA_BYTES_MIN = 0x07; + static constexpr int THEORA_HEADER_TYPE = 0x80; + static constexpr double REWIND_THRESHOLD = 0.01; + + StreamType FindStream(); + + bool ReadPacket(ogg_packet& packet, bool mustSucceed = false); + + void ReSync(); + + bool IsEOS() const; + + const std::string& GetFilename() const; + + bool Seek(ogg_packet& packet, double target, std::function getTime); + + private: + StrongReference file; + + ogg_sync_state sync; + ogg_stream_state stream; + ogg_page page; + + bool intiialized; + int serial; + bool endOfStream; + + bool ReadPage(bool errorEOF = false); + + StreamType DetermineType(); + }; +} // namespace love diff --git a/include/objects/videostream/theora/theorastreamc.h b/include/objects/videostream/theora/theorastreamc.h new file mode 100644 index 000000000..e30f8971d --- /dev/null +++ b/include/objects/videostream/theora/theorastreamc.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include + +#include "modules/thread/types/mutex.h" + +#include "objects/file/file.h" +#include "objects/videostream/theora/oggdemuxer.h" +#include "objects/videostream/videostream.h" + +namespace love::common +{ + class TheoraStream : public VideoStream + { + public: + TheoraStream(File* file); + + ~TheoraStream(); + + const void* GetFrontBuffer() const; + + virtual size_t GetSize() const = 0; + + void FillBackBuffer(); + + bool SwapBuffers(); + + int GetWidth() const; + + int GetHeight() const; + + const std::string& GetFilename() const; + + void SetSync(FrameSync* other); + + bool IsPlaying() const; + + void ThreadedFillBackBuffer(double dt); + + virtual void SetupBuffers() = 0; + + virtual void FillBufferData(th_ycbcr_buffer bufferInfo) = 0; + + protected: + IFrame* frontBuffer; + IFrame* backBuffer; + + OggDemuxer demuxer; + bool headerParsed; + + ogg_packet packet; + + th_info info; + th_dec_ctx* decoder; + + thread::MutexRef bufferMutex; + bool frameReady; + + double lastFrame; + double nextFrame; + + struct PostProcess + { + int current; + int maximum; + int offset; + } quality; + + void ParseHeader(); + + void SeekDecoder(double target); + }; +} // namespace love::common diff --git a/include/objects/videostream/utility/stream.h b/include/objects/videostream/utility/stream.h new file mode 100644 index 000000000..d151ff1ed --- /dev/null +++ b/include/objects/videostream/utility/stream.h @@ -0,0 +1,24 @@ +#pragma once + +#include "objects/object.h" + +namespace love +{ + class Stream : public Object + { + public: + static love::Type type; + + virtual ~Stream() + {} + + virtual void FillBackBuffer() + {} + + virtual const void* GetFrontBuffer() const = 0; + + virtual size_t GetSize() const = 0; + + virtual bool SwapBuffers() = 0; + }; +} // namespace love diff --git a/include/objects/videostream/videostream.h b/include/objects/videostream/videostream.h new file mode 100644 index 000000000..2b5845511 --- /dev/null +++ b/include/objects/videostream/videostream.h @@ -0,0 +1,54 @@ +#pragma once + +#include "common/strongref.h" + +#include "objects/videostream/sync/framesync.h" +#include "objects/videostream/utility/stream.h" + +namespace love +{ + class VideoStream : public Stream + { + public: + static love::Type type; + + struct IFrame + { + IFrame() + {} + + ~IFrame() + {} + }; + + virtual ~VideoStream() + {} + + virtual int GetWidth() const = 0; + + virtual int GetHeight() const = 0; + + virtual const std::string& GetFilename() const = 0; + + /* playback api */ + + virtual void Play(); + + virtual void Pause(); + + virtual void Seek(double offset); + + virtual double Tell() const; + + virtual bool IsPlaying() const; + + /* sync stuff */ + + virtual void SetSync(FrameSync* sync); + + virtual FrameSync* GetSync() const; + + protected: + StrongReference frameSync; + }; +} // namespace love diff --git a/include/objects/videostream/wrap_videostream.h b/include/objects/videostream/wrap_videostream.h new file mode 100644 index 000000000..47c72c5c1 --- /dev/null +++ b/include/objects/videostream/wrap_videostream.h @@ -0,0 +1,27 @@ +#pragma once + +#include "common/luax.h" +#include "objects/videostream/videostream.h" + +namespace Wrap_VideoStream +{ + int SetSync(lua_State* L); + + int GetFilename(lua_State* L); + + int Play(lua_State* L); + + int Pause(lua_State* L); + + int Seek(lua_State* L); + + int Rewind(lua_State* L); + + int Tell(lua_State* L); + + int IsPlaying(lua_State* L); + + love::VideoStream* CheckVideoStream(lua_State* L, int index); + + int Register(lua_State* L); +} // namespace Wrap_VideoStream diff --git a/libraries/https/common/HTTPRequest.cpp b/libraries/https/common/HTTPRequest.cpp index 51857ccf8..23cb10551 100644 --- a/libraries/https/common/HTTPRequest.cpp +++ b/libraries/https/common/HTTPRequest.cpp @@ -7,7 +7,7 @@ #include "HTTPRequest.h" #include "PlaintextConnection.h" -HTTPRequest::HTTPRequest(ConnectionFactory factory) : factory(factory), method("") +HTTPRequest::HTTPRequest(ConnectionFactory factory) : factory(factory) {} HTTPSClient::Reply HTTPRequest::request(const HTTPSClient::Request& req) diff --git a/libraries/https/common/HTTPSClient.cpp b/libraries/https/common/HTTPSClient.cpp index a7dd25d60..3729cb939 100644 --- a/libraries/https/common/HTTPSClient.cpp +++ b/libraries/https/common/HTTPSClient.cpp @@ -29,5 +29,5 @@ bool HTTPSClient::ci_string_less::operator()(const std::string& lhs, const std:: return false; } -HTTPSClient::Request::Request(const std::string& url) : url(url), method(GET) +HTTPSClient::Request::Request(const std::string& url) : url(url), method("") {} diff --git a/libraries/https/generic/CurlClient.cpp b/libraries/https/generic/CurlClient.cpp index 22a75d47a..2b09ed0c2 100644 --- a/libraries/https/generic/CurlClient.cpp +++ b/libraries/https/generic/CurlClient.cpp @@ -62,12 +62,22 @@ HTTPSClient::Reply CurlClient::request(const HTTPSClient::Request& req) curl_easy_setopt(handle, CURLOPT_URL, req.url.c_str()); curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); - if (req.method == Request::POST) + if (req.method == "PUT") + curl_easy_setopt(handle, CURLOPT_PUT, 1L); + if (req.method == "POST") { curl_easy_setopt(handle, CURLOPT_POST, 1L); curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req.postdata.c_str()); curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, req.postdata.size()); } + else + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, req.method.c_str()); + + if (req.postdata.size() > 0 && (req.method != "GET" && req.method != "HEAD")) + { + curl_easy_setopt(handle, CURLOPT_POSTFIELDS, req.postdata.c_str()); + curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, req.postdata.size()); + } // Curl doesn't copy memory, keep the strings around std::vector lines; diff --git a/lovepotion.code-workspace b/lovepotion.code-workspace index abfed5732..ab7e1c54a 100644 --- a/lovepotion.code-workspace +++ b/lovepotion.code-workspace @@ -68,6 +68,9 @@ "numbers": "cpp", "span": "cpp" }, - "terminal.integrated.defaultProfile.windows": "msys2" + "terminal.integrated.defaultProfile.windows": "msys2", + "yaml.schemas": { + "https://json.schemastore.org/github-workflow.json": "file:///g%3A/GitHub/C%2B%2B/lovepotion/.github/workflows/build.yml" + } } } diff --git a/platform/3ds/include/citro2d/citro.h b/platform/3ds/include/citro2d/citro.h index 803e9f60b..52513fe80 100644 --- a/platform/3ds/include/citro2d/citro.h +++ b/platform/3ds/include/citro2d/citro.h @@ -2,8 +2,10 @@ #include "common/lmath.h" #include "objects/canvas/canvas.h" + #include +#include "common/pixelformat.h" #include "graphics/graphics.h" class citro2d @@ -91,6 +93,10 @@ class citro2d static GPUFilter GetCitroFilterMode(const love::Texture::Filter& mode); + static bool GetConstant(love::PixelFormat in, GPU_TEXCOLOR& out); + + static bool GetConstant(GPU_TEXCOLOR in, love::PixelFormat& out); + private: GPUFilter filter; diff --git a/platform/3ds/include/objects/video/video.h b/platform/3ds/include/objects/video/video.h new file mode 100644 index 000000000..3ba7471e5 --- /dev/null +++ b/platform/3ds/include/objects/video/video.h @@ -0,0 +1,24 @@ +#pragma once + +#include "objects/video/videoc.h" + +#include + +namespace love +{ + class Video : public common::Video + { + public: + Video(Graphics* graphics, VideoStream* stream, float dpiScale = 1.0f); + + virtual ~Video(); + + void Draw(Graphics* graphics, const Matrix4& matrix) override; + + protected: + void Update() override; + + private: + C2D_Image current; + }; +} // namespace love diff --git a/platform/3ds/include/objects/videostream/theorastream.h b/platform/3ds/include/objects/videostream/theorastream.h new file mode 100644 index 000000000..d2d174ecc --- /dev/null +++ b/platform/3ds/include/objects/videostream/theorastream.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "objects/videostream/theora/theorastreamc.h" + +namespace love +{ + class TheoraStream : public common::TheoraStream + { + public: + TheoraStream(File* file); + + struct Frame : VideoStream::IFrame + { + Frame(); + + ~Frame(); + + C3D_Tex* buffer; + int width, height; + }; + + virtual size_t GetSize() const override; + + virtual void SetupBuffers() override; + + virtual void FillBufferData(th_ycbcr_buffer bufferInfo) override; + + private: + void SetPostProcessingLevel(); + + th_pixel_fmt format; + int width, height; + Handle handle; + }; +} // namespace love diff --git a/platform/3ds/source/citro2d/citro.cpp b/platform/3ds/source/citro2d/citro.cpp index 6f4201255..4d0391b57 100644 --- a/platform/3ds/source/citro2d/citro.cpp +++ b/platform/3ds/source/citro2d/citro.cpp @@ -6,8 +6,13 @@ #include "citro2d/citro.h" +#include "common/bidirectionalmap.h" #include "modules/graphics/graphics.h" +#include "common/pixelformat.h" + +using namespace love; + citro2d::citro2d() { gfxInitDefault(); @@ -234,3 +239,24 @@ GPU_TEXTURE_WRAP_PARAM citro2d::GetCitroWrapMode(love::Texture::WrapMode wrap) return GPU_MIRRORED_REPEAT; } } + +// clang-format off +constexpr auto pixelFormats = BidirectionalMap<>::Create( + PIXELFORMAT_TEX3DS_RGBA8, GPU_RGBA8, + PIXELFORMAT_RGBA8, GPU_RGBA8, + PIXELFORMAT_RGB8, GPU_RGB8, + PIXELFORMAT_RGB565, GPU_RGB565, + PIXELFORMAT_LA8, GPU_LA8, + PIXELFORMAT_ETC1, GPU_ETC1 +); +// clang-format on + +bool citro2d::GetConstant(PixelFormat in, GPU_TEXCOLOR& out) +{ + return pixelFormats.Find(in, out); +} + +bool citro2d::GetConstant(GPU_TEXCOLOR in, PixelFormat& out) +{ + return pixelFormats.ReverseFind(in, out); +} diff --git a/platform/3ds/source/objects/image.cpp b/platform/3ds/source/objects/image.cpp index 52a552554..f8daeae8e 100644 --- a/platform/3ds/source/objects/image.cpp +++ b/platform/3ds/source/objects/image.cpp @@ -1,6 +1,9 @@ #include "objects/image/image.h" #include "modules/graphics/graphics.h" +#include "citro2d/citro.h" +#include "common/pixelformat.h" + using namespace love; Image::Image(const Slices& slices) : Image(slices, true) @@ -35,11 +38,18 @@ void Image::Init(PixelFormat format, int width, int height) unsigned powTwoWidth = NextPO2(width + 2); unsigned powTwoHeight = NextPO2(height + 2); - if (!C3D_TexInit(this->texture.tex, powTwoWidth, powTwoHeight, GPU_RGBA8)) + GPU_TEXCOLOR color; + ::citro2d::GetConstant(format, color); + + if (!C3D_TexInit(this->texture.tex, powTwoWidth, powTwoHeight, color)) throw love::Exception("Failed to initialize texture!"); - size_t copySize = powTwoWidth * powTwoHeight * 4; - memcpy(this->texture.tex->data, this->data.Get(0, 0)->GetData(), copySize); + size_t copySize = powTwoWidth * powTwoHeight * GetPixelFormatSize(format); + + if (this->data.Get(0, 0)) + memcpy(this->texture.tex->data, this->data.Get(0, 0)->GetData(), copySize); + else + memset(this->texture.tex->data, 0, copySize); C3D_TexFlush(this->texture.tex); @@ -53,6 +63,52 @@ void Image::Init(PixelFormat format, int width, int height) this->SetFilter(this->filter); this->SetWrap(this->wrap); } +#include "debug/logger.h" +void Image::ReplacePixels(const void* data, size_t size, const Rect& rect) +{ + if (!this->texture.tex) + throw love::Exception("Failed to replace pixels. Texture is uninitialized."); + + if (size == 0) + throw love::Exception("Failed to replace pixels. Data is nullptr."); + + size_t srcPowTwoWidth = NextPO2(rect.w + 2); + size_t srcPowTwoHeight = NextPO2(rect.h + 2); + + if (this->texture.tex->width == srcPowTwoWidth && this->texture.tex->height == srcPowTwoHeight) + { + memcpy(this->texture.tex->data, data, size); + return; + } + + /* love::Rect should be Po2 already */ + + auto getFunction = ImageData::GetPixelGetFunction(this->format); + auto setFunction = ImageData::GetPixelSetFunction(this->format); + + for (int y = 0; y < rect.h; y++) + { + for (int x = 0; x < rect.w; x++) + { + unsigned srcIndex = coordToIndex(srcPowTwoWidth, x + 1, y + 1); + unsigned dstIndex = coordToIndex(this->texture.tex->width, x + 1, y + 1); + + Colorf color {}; + + /* grab the pixel data from our source */ + const ImageData::Pixel* srcPixel = + reinterpret_cast((uint32_t*)data + srcIndex); + getFunction(srcPixel, color); + + /* set the pixel we got to ours */ + ImageData::Pixel* dstPixel = + reinterpret_cast((uint32_t*)this->texture.tex->data + dstIndex); + setFunction(color, dstPixel); + } + } + + C3D_TexFlush(this->texture.tex); +} Image::~Image() { diff --git a/platform/3ds/source/objects/theorastream.cpp b/platform/3ds/source/objects/theorastream.cpp new file mode 100644 index 000000000..f65fe2802 --- /dev/null +++ b/platform/3ds/source/objects/theorastream.cpp @@ -0,0 +1,161 @@ +#include "common/lmath.h" + +#include "modules/thread/types/lock.h" +#include "objects/videostream/theorastream.h" + +#include "citro2d/citro.h" +#include "common/pixelformat.h" + +using namespace love; + +TheoraStream::Frame::Frame() +{ + this->buffer = new C3D_Tex(); +} + +TheoraStream::Frame::~Frame() +{ + C3D_TexDelete(this->buffer); + delete this->buffer; +} + +TheoraStream::TheoraStream(File* file) : common::TheoraStream(file) +{ + this->frontBuffer = new Frame(); + this->backBuffer = new Frame(); + + th_info_init(&this->info); + + try + { + this->ParseHeader(); + } + catch (love::Exception& exception) + { + delete this->frontBuffer; + delete this->backBuffer; + + th_info_clear(&this->info); + + throw exception; + } +} + +size_t TheoraStream::GetSize() const +{ + return sizeof(Frame); +} + +void TheoraStream::SetupBuffers() +{ + switch (this->info.pixel_fmt) + { + case TH_PF_420: + break; + case TH_PF_422: + break; + case TH_PF_444: + throw love::Exception("YUV444 is not supported by Y2R"); + return; + case TH_PF_RSVD: + default: + throw love::Exception("UNKNOWN Chroma sampling!"); + return; + } + + this->format = this->info.pixel_fmt; + + int calcWidth = + ((this->info.pic_x + this->info.frame_width + 1) & ~1) - (this->info.pic_x & ~1); + int calcHeight = + ((this->info.pic_y + this->info.frame_height + 1) & ~1) - (this->info.pic_y & ~1); + + this->width = calcWidth; + this->height = calcHeight; + + int powTwoWidth = NextPO2(calcWidth); + int powTwoHeight = NextPO2(calcHeight); + + Frame* buffers[2] = { (Frame*)this->backBuffer, (Frame*)this->frontBuffer }; + + for (int index = 0; index < 2; index++) + { + C3D_Tex* texture = buffers[index]->buffer; + + C3D_TexInit(texture, powTwoWidth, powTwoHeight, GPU_RGB8); + C3D_TexSetFilter(texture, GPU_LINEAR, GPU_LINEAR); + + memset(texture->data, 0, texture->size); + + buffers[index]->width = calcWidth; + buffers[index]->height = calcHeight; + } +} + +void TheoraStream::SetPostProcessingLevel() +{ + if (this->quality.offset) + { + this->quality.current += this->quality.offset; + th_decode_ctl(this->decoder, TH_DECCTL_SET_PPLEVEL, &this->quality.current, + sizeof(this->quality.current)); + this->quality.offset = 0; + } +} + +void TheoraStream::FillBufferData(th_ycbcr_buffer bufferInfo) +{ + bool isBusy = true; + + Y2RU_StopConversion(); + + while (isBusy) + Y2RU_IsBusyConversion(&isBusy); + + switch (this->format) + { + case TH_PF_420: + Y2RU_SetInputFormat(Y2RU_InputFormat::INPUT_YUV420_INDIV_8); + break; + case TH_PF_422: + Y2RU_SetInputFormat(Y2RU_InputFormat::INPUT_YUV422_INDIV_8); + break; + default: + break; + } + + Y2RU_SetOutputFormat(Y2RU_OutputFormat::OUTPUT_RGB_24); + Y2RU_SetRotation(Y2RU_Rotation::ROTATION_NONE); + Y2RU_SetBlockAlignment(Y2RU_BlockAlignment::BLOCK_8_BY_8); + Y2RU_SetTransferEndInterrupt(true); + Y2RU_SetInputLineWidth(this->width); + Y2RU_SetInputLines(this->height); + Y2RU_SetStandardCoefficient(Y2RU_StandardCoefficient::COEFFICIENT_ITU_R_BT_601_SCALING); + Y2RU_SetAlpha(0xFF); + + /* set up the YUV data for Y2RU */ + + Y2RU_SetSendingY(bufferInfo[0].data, this->width * this->height, this->width, + bufferInfo[0].stride - this->width); + + Y2RU_SetSendingU(bufferInfo[1].data, (this->width / 2) * (this->height / 2), this->width / 2, + bufferInfo[1].stride - (this->width >> 1)); + + Y2RU_SetSendingV(bufferInfo[2].data, (this->width / 2) * (this->height / 2), this->width / 2, + bufferInfo[2].stride - (this->width >> 1)); + + PixelFormat format; + ::citro2d::GetConstant(((Frame*)this->backBuffer)->buffer->fmt, format); + + size_t formatSize = GetPixelFormatSize(format); + + Y2RU_SetReceiving(((Frame*)this->backBuffer)->buffer->data, + this->width * this->height * formatSize, this->width * 8 * formatSize, + (NextPO2(this->width) - this->width) * 8 * formatSize); + + /* convert the data */ + + Y2RU_StartConversion(); + + Y2RU_GetTransferEndEvent(&this->handle); +} diff --git a/platform/3ds/source/objects/video.cpp b/platform/3ds/source/objects/video.cpp new file mode 100644 index 000000000..a80a55855 --- /dev/null +++ b/platform/3ds/source/objects/video.cpp @@ -0,0 +1,50 @@ +#include "objects/video/video.h" +#include "objects/videostream/theorastream.h" + +#include "modules/graphics/graphics.h" + +using namespace love; + +Video::Video(Graphics* graphics, VideoStream* stream, float dpiScale) : + common::Video(graphics, stream, dpiScale) +{ + this->stream->FillBackBuffer(); + + auto frame = (const TheoraStream::Frame*)this->stream->GetFrontBuffer(); + Rect rect = { 0, 0, frame->width, frame->height }; + + /* initialize all these StrongReference items, so setFilter will be happy */ + for (int index = 0; index < 3; index++) + { + Image* image = graphics->NewImage(Texture::TEXTURE_2D, PixelFormat::PIXELFORMAT_RGB8, + rect.w, rect.h, 1); + + image->ReplacePixels(frame->buffer->data, frame->buffer->size, rect); + this->images[index].Set(image, Acquire::NORETAIN); + } +} + +Video::~Video() +{} + +void Video::Update() +{ + bool buffersChanged = this->stream->SwapBuffers(); + this->stream->FillBackBuffer(); + + if (buffersChanged) + { + auto frame = (const TheoraStream::Frame*)this->stream->GetFrontBuffer(); + Rect rect = { 0, 0, frame->width, frame->height }; + + for (int index = 0; index < 1; index++) + this->images[index]->ReplacePixels(frame->buffer->data, frame->buffer->size, rect); + } +} + +void Video::Draw(Graphics* graphics, const Matrix4& localTransform) +{ + this->Update(); + + this->images[0]->Draw(graphics, localTransform); +} diff --git a/platform/3ds/source/runtime.cpp b/platform/3ds/source/runtime.cpp index d320953a6..51324ea93 100644 --- a/platform/3ds/source/runtime.cpp +++ b/platform/3ds/source/runtime.cpp @@ -33,6 +33,8 @@ extern "C" SOCKET_BUFFER = (u32*)memalign(BUFFER_ALIGN, SOC_BUFSIZE); R_ABORT_LAMBDA_UNLESS(socInit(SOCKET_BUFFER, SOC_BUFSIZE), [&]() { free(SOCKET_BUFFER); }); + R_ABORT_UNLESS(y2rInit()); + /* accelerometer */ HIDUSER_EnableAccelerometer(); @@ -45,6 +47,8 @@ extern "C" void userAppExit() { + y2rExit(); + HIDUSER_DisableGyroscope(); HIDUSER_DisableAccelerometer(); diff --git a/platform/switch/include/deko3d/deko.h b/platform/switch/include/deko3d/deko.h index 390155029..12263fa90 100644 --- a/platform/switch/include/deko3d/deko.h +++ b/platform/switch/include/deko3d/deko.h @@ -147,6 +147,8 @@ class deko3d bool RenderTexture(const DkResHandle handle, const vertex::Vertex* points, size_t count); + bool RenderVideo(const DkResHandle handles[3], const vertex::Vertex* points, size_t count); + /* Primitives Rendering */ bool RenderPolygon(const vertex::Vertex* points, size_t count); @@ -173,6 +175,7 @@ class deko3d { STATE_PRIMITIVE, STATE_TEXTURE, + STATE_VIDEO, STATE_MAX_ENUM }; diff --git a/platform/switch/include/deko3d/graphics.h b/platform/switch/include/deko3d/graphics.h index a0e76c141..0b51e8ab2 100644 --- a/platform/switch/include/deko3d/graphics.h +++ b/platform/switch/include/deko3d/graphics.h @@ -34,9 +34,6 @@ namespace love::deko3d /* Primitives */ - love::Image* NewImage(Texture::TextureType t, PixelFormat format, int width, int height, - int slices); - void Rectangle(DrawMode mode, float x, float y, float width, float height) override; void Rectangle(DrawMode mode, float x, float y, float width, float height, float rx, diff --git a/platform/switch/include/deko3d/shader.h b/platform/switch/include/deko3d/shader.h index a9a7ae90e..c09f62af6 100644 --- a/platform/switch/include/deko3d/shader.h +++ b/platform/switch/include/deko3d/shader.h @@ -25,6 +25,7 @@ namespace love { STANDARD_DEFAULT, STANDARD_TEXTURE, + STANDARD_VIDEO, STANDARD_MAX_ENUM }; diff --git a/platform/switch/include/modules/timer/timer.h b/platform/switch/include/modules/timer/timer.h index 8b84c98c8..03229882d 100644 --- a/platform/switch/include/modules/timer/timer.h +++ b/platform/switch/include/modules/timer/timer.h @@ -12,4 +12,4 @@ namespace love virtual ~Timer() {} }; -} // namespace love \ No newline at end of file +} // namespace love diff --git a/platform/switch/include/objects/video/video.h b/platform/switch/include/objects/video/video.h new file mode 100644 index 000000000..edc5150a6 --- /dev/null +++ b/platform/switch/include/objects/video/video.h @@ -0,0 +1,23 @@ +#pragma once + +#include "deko3d/vertex.h" +#include "objects/video/videoc.h" + +namespace love +{ + class Video : public common::Video + { + public: + Video(Graphics* graphics, VideoStream* stream, float dpiScale = 1.0f); + + virtual ~Video(); + + void Draw(Graphics* graphics, const Matrix4& matrix) override; + + protected: + void Update() override; + + private: + vertex::Vertex vertices[4]; + }; +} // namespace love diff --git a/platform/switch/include/objects/videostream/theorastream.h b/platform/switch/include/objects/videostream/theorastream.h new file mode 100644 index 000000000..f9a4169a9 --- /dev/null +++ b/platform/switch/include/objects/videostream/theorastream.h @@ -0,0 +1,38 @@ +#pragma once + +#include "objects/videostream/theora/theorastreamc.h" + +namespace love +{ + class TheoraStream : public common::TheoraStream + { + public: + TheoraStream(File* file); + + struct Frame : VideoStream::IFrame + { + Frame(); + + ~Frame(); + + int yw, yh; + uint8_t* yPlane; + + int cw, ch; + uint8_t* cbPlane; + uint8_t* crPlane; + }; + + virtual size_t GetSize() const override; + + virtual void SetupBuffers() override; + + virtual void FillBufferData(th_ycbcr_buffer bufferInfo) override; + + private: + unsigned yPlaneXOffset; + unsigned cPlaneXOffset; + unsigned yPlaneYOffset; + unsigned cPlaneYOffset; + }; +} // namespace love diff --git a/platform/switch/shaders/video_fsh.glsl b/platform/switch/shaders/video_fsh.glsl new file mode 100644 index 000000000..b05092e59 --- /dev/null +++ b/platform/switch/shaders/video_fsh.glsl @@ -0,0 +1,32 @@ +#version 460 + +layout (location = 0) in vec4 inColor; +layout (location = 1) in vec2 inTexCoord; + +layout (location = 0) out vec4 outColor; + +layout (binding = 0) uniform sampler2D video_y; +layout (binding = 1) uniform sampler2D video_cb; +layout (binding = 2) uniform sampler2D video_cr; + +const vec3 yuv_add = vec3(-0.0627451017, -0.501960814, -0.501960814); + +const vec3 yuv_r = vec3(1.164, 0.000, 1.596); +const vec3 yuv_g = vec3(1.164, -0.391, -0.813); +const vec3 yuv_b = vec3(1.164, 2.018, 0.000); + +void main() +{ + vec3 yuv; + + yuv[0] = texture(video_y, inTexCoord).r; + yuv[1] = texture(video_cb, inTexCoord).r; + yuv[2] = texture(video_cr, inTexCoord).r; + + yuv += yuv_add; + + outColor[0] = dot(yuv, yuv_r); + outColor[1] = dot(yuv, yuv_g); + outColor[2] = dot(yuv, yuv_b); + outColor[3] = 1.0; +} diff --git a/platform/switch/source/deko3d/deko.cpp b/platform/switch/source/deko3d/deko.cpp index cc53c8618..4a6e54416 100644 --- a/platform/switch/source/deko3d/deko.cpp +++ b/platform/switch/source/deko3d/deko.cpp @@ -194,9 +194,12 @@ void deko3d::EnsureInState(State state) this->cmdBuf.bindVtxAttribState(vertex::attributes::PrimitiveAttribState); this->cmdBuf.bindVtxBufferState(vertex::attributes::PrimitiveBufferState); } - else if (this->renderState == STATE_TEXTURE) + else if (this->renderState == STATE_TEXTURE || this->renderState == STATE_VIDEO) { - love::Shader::standardShaders[love::Shader::STANDARD_TEXTURE]->Attach(); + if (this->renderState == STATE_TEXTURE) + love::Shader::standardShaders[love::Shader::STANDARD_TEXTURE]->Attach(); + else + love::Shader::standardShaders[love::Shader::STANDARD_VIDEO]->Attach(); this->cmdBuf.bindVtxAttribState(vertex::attributes::TextureAttribState); this->cmdBuf.bindVtxBufferState(vertex::attributes::TextureBufferState); @@ -387,6 +390,30 @@ bool deko3d::RenderTexture(const DkResHandle handle, const vertex::Vertex* point return true; } +bool deko3d::RenderVideo(const DkResHandle handles[3], const vertex::Vertex* points, size_t count) +{ + if (count > (this->vtxRing.getSize() - this->firstVertex) || points == nullptr) + return false; + + this->EnsureInState(STATE_VIDEO); + + if (this->descriptorsDirty) + { + this->cmdBuf.barrier(DkBarrier_Primitives, DkInvalidateFlags_Descriptors); + this->descriptorsDirty = false; + } + + this->cmdBuf.bindTextures(DkStage_Fragment, 0, { handles[0], handles[1], handles[2] }); + + memcpy(this->vertexData + this->firstVertex, points, count * sizeof(vertex::Vertex)); + + this->cmdBuf.draw(DkPrimitive_Quads, count, 1, this->firstVertex, 0); + + this->firstVertex += count; + + return true; +} + bool deko3d::RenderPolyline(DkPrimitive mode, const vertex::Vertex* points, size_t count) { if (count > (this->vtxRing.getSize() - this->firstVertex) || points == nullptr) @@ -595,6 +622,7 @@ DkWrapMode deko3d::GetDekoWrapMode(love::Texture::WrapMode wrap) // clang-format off constexpr auto pixelFormats = BidirectionalMap<>::Create( + PIXELFORMAT_R8, DkImageFormat_R8_Unorm, PIXELFORMAT_RGBA8, DkImageFormat_RGBA8_Unorm, PIXELFORMAT_DXT1, DkImageFormat_RGBA_BC1, PIXELFORMAT_DXT3, DkImageFormat_RGBA_BC2, diff --git a/platform/switch/source/deko3d/graphics.cpp b/platform/switch/source/deko3d/graphics.cpp index 10d4059d3..dddc64bdf 100644 --- a/platform/switch/source/deko3d/graphics.cpp +++ b/platform/switch/source/deko3d/graphics.cpp @@ -101,12 +101,6 @@ void love::deko3d::Graphics::SetColor(Colorf color) ::deko3d::Instance().SetBlendColor(color); } -love::Image* love::deko3d::Graphics::NewImage(Texture::TextureType t, PixelFormat format, int width, - int height, int slices) -{ - return new Image(t, format, width, height, slices); -} - void love::deko3d::Graphics::SetMeshCullMode(vertex::CullMode mode) { DkFace face = DkFace_None; diff --git a/platform/switch/source/deko3d/shader.cpp b/platform/switch/source/deko3d/shader.cpp index cb98a986b..cb2fd9e5a 100644 --- a/platform/switch/source/deko3d/shader.cpp +++ b/platform/switch/source/deko3d/shader.cpp @@ -15,6 +15,7 @@ Shader* love::Shader::standardShaders[love::Shader::STANDARD_MAX_ENUM] = { nullp #define DEFAULT_VERTEX_SHADER (SHADERS_DIR "transform_vsh.dksh") #define DEFAULT_FRAGMENT_SHADER (SHADERS_DIR "color_fsh.dksh") #define DEFAULT_TEXTURE_SHADER (SHADERS_DIR "texture_fsh.dksh") +#define DEFAULT_VIDEO_SHADER (SHADERS_DIR "video_fsh.dksh") Shader::Shader() : program() {} @@ -56,6 +57,9 @@ void Shader::LoadDefaults(StandardShader type) this->program.vertex->load(::deko3d::Instance().GetCode(), DEFAULT_VERTEX_SHADER); this->program.fragment->load(::deko3d::Instance().GetCode(), DEFAULT_TEXTURE_SHADER); break; + case STANDARD_VIDEO: + this->program.vertex->load(::deko3d::Instance().GetCode(), DEFAULT_VERTEX_SHADER); + this->program.fragment->load(::deko3d::Instance().GetCode(), DEFAULT_VIDEO_SHADER); default: break; } @@ -135,7 +139,8 @@ void Shader::Attach() // clang-format off constexpr auto shaderNames = BidirectionalMap<>::Create( "default", Shader::StandardShader::STANDARD_DEFAULT, - "texture", Shader::StandardShader::STANDARD_TEXTURE + "texture", Shader::StandardShader::STANDARD_TEXTURE, + "video", Shader::StandardShader::STANDARD_VIDEO ); // clang-format on diff --git a/platform/switch/source/objects/theorastream.cpp b/platform/switch/source/objects/theorastream.cpp new file mode 100644 index 000000000..aedd3bebb --- /dev/null +++ b/platform/switch/source/objects/theorastream.cpp @@ -0,0 +1,112 @@ +#include "objects/videostream/theorastream.h" + +#include "modules/thread/types/lock.h" + +using namespace love; + +TheoraStream::Frame::Frame() : yPlane(nullptr), cbPlane(nullptr), crPlane(nullptr) +{} + +TheoraStream::Frame::~Frame() +{ + delete[] this->yPlane; + delete[] this->cbPlane; + delete[] this->crPlane; +} + +TheoraStream::TheoraStream(File* file) : common::TheoraStream(file) +{ + this->frontBuffer = new Frame(); + this->backBuffer = new Frame(); + + th_info_init(&this->info); + + try + { + this->ParseHeader(); + } + catch (love::Exception& exception) + { + delete this->backBuffer; + delete this->frontBuffer; + + th_info_clear(&this->info); + + throw exception; + } +} + +template +inline void scaleFormat(th_pixel_fmt format, T& x, T& y) +{ + switch (format) + { + case TH_PF_420: + y /= 2; + case TH_PF_422: + x /= 2; + break; + default: + break; + } +} + +size_t TheoraStream::GetSize() const +{ + return sizeof(Frame); +} + +void TheoraStream::SetupBuffers() +{ + Frame* buffers[2] = { (Frame*)this->backBuffer, (Frame*)this->frontBuffer }; + + this->yPlaneXOffset = this->cPlaneXOffset = this->info.pic_x; + this->yPlaneYOffset = this->cPlaneYOffset = this->info.pic_y; + + scaleFormat(this->info.pixel_fmt, cPlaneXOffset, cPlaneYOffset); + + for (size_t index = 0; index < 2; index++) + { + buffers[index]->cw = buffers[index]->yw = this->info.pic_width; + buffers[index]->ch = buffers[index]->yh = this->info.pic_height; + + scaleFormat(this->info.pixel_fmt, buffers[index]->cw, buffers[index]->ch); + + buffers[index]->yPlane = new uint8_t[buffers[index]->yw * buffers[index]->yh]; + buffers[index]->cbPlane = new uint8_t[buffers[index]->cw * buffers[index]->ch]; + buffers[index]->crPlane = new uint8_t[buffers[index]->cw * buffers[index]->ch]; + + memset(buffers[index]->yPlane, 16, buffers[index]->yw * buffers[index]->yh); + memset(buffers[index]->cbPlane, 128, buffers[index]->cw * buffers[index]->ch); + memset(buffers[index]->crPlane, 128, buffers[index]->cw * buffers[index]->ch); + } +} + +void TheoraStream::FillBufferData(th_ycbcr_buffer bufferInfo) +{ + if (!bufferInfo[0].data || !bufferInfo[1].data || !bufferInfo[2].data) + return; + + Frame* frame = (Frame*)this->backBuffer; + + for (int y = 0; y < frame->yh; ++y) + { + memcpy(frame->yPlane + frame->yw * y, + bufferInfo[0].data + bufferInfo[0].stride * (y + yPlaneYOffset) + yPlaneXOffset, + frame->yw); + } + + for (int y = 0; y < frame->ch; ++y) + { + memcpy(frame->cbPlane + frame->cw * y, + bufferInfo[1].data + bufferInfo[1].stride * (y + cPlaneYOffset) + cPlaneXOffset, + frame->cw); + } + + for (int y = 0; y < frame->ch; ++y) + { + memcpy(frame->crPlane + frame->cw * y, + bufferInfo[2].data + bufferInfo[2].stride * (y + cPlaneYOffset) + cPlaneXOffset, + frame->cw); + } +} diff --git a/platform/switch/source/objects/video.cpp b/platform/switch/source/objects/video.cpp new file mode 100644 index 000000000..b7f51c08f --- /dev/null +++ b/platform/switch/source/objects/video.cpp @@ -0,0 +1,122 @@ +#include "objects/video/video.h" + +#include "modules/graphics/graphics.h" +#include "objects/videostream/theorastream.h" + +#include "deko3d/deko.h" + +using namespace love; + +Video::Video(Graphics* graphics, VideoStream* stream, float dpiScale) : + common::Video(graphics, stream, dpiScale) +{ + std::fill_n(this->vertices, 4, vertex::Vertex {}); + + this->vertices[0] = { .position = { 0, 0 }, .color = { 1, 1, 1, 1 }, .texcoord = { 0, 0 } }; + + this->vertices[1] = { .position = { 0, (float)this->height }, + .color = { 1, 1, 1, 1 }, + .texcoord = { 0, 1 } }; + + this->vertices[2] = { .position = { (float)this->width, (float)this->height }, + .color = { 1, 1, 1, 1 }, + .texcoord = { 1, 1 } }; + + this->vertices[3] = { .position = { (float)this->width, 0 }, + .color = { 1, 1, 1, 1 }, + .texcoord = { 1, 0 } }; + + auto frame = (const TheoraStream::Frame*)this->stream->GetFrontBuffer(); + + int widths[3] = { frame->yw, frame->cw, frame->cw }; + int heights[3] = { frame->yh, frame->ch, frame->ch }; + + const uint8_t* data[3] = { frame->yPlane, frame->cbPlane, frame->crPlane }; + + Texture::Wrap wrap; + for (size_t index = 0; index < 3; index++) + { + Image* image = graphics->NewImage(Texture::TEXTURE_2D, PIXELFORMAT_R8, widths[index], + heights[index], 1); + + image->SetFilter(this->filter); + image->SetWrap(wrap); + + size_t formatSize = GetPixelFormatSize(PIXELFORMAT_R8); + size_t size = widths[index] * heights[index] * formatSize; + + Rect rect = { 0, 0, widths[index], heights[index] }; + image->ReplacePixels(data[index], size, rect); + + this->images[index].Set(image, Acquire::NORETAIN); + } +} + +Video::~Video() +{} + +void Video::Update() +{ + bool buffersChanged = this->stream->SwapBuffers(); + this->stream->FillBackBuffer(); + + if (buffersChanged) + { + auto frame = (const TheoraStream::Frame*)this->stream->GetFrontBuffer(); + + int widths[3] = { frame->yw, frame->cw, frame->cw }; + int heights[3] = { frame->yh, frame->ch, frame->ch }; + + const uint8_t* data[3] = { frame->yPlane, frame->cbPlane, frame->crPlane }; + + for (int i = 0; i < 3; i++) + { + size_t formatSize = GetPixelFormatSize(PIXELFORMAT_R8); + + size_t size = widths[i] * heights[i] * formatSize; + + Rect rect = { 0, 0, widths[i], heights[i] }; + images[i]->ReplacePixels(data[i], size, rect); + } + } +} + +void Video::Draw(Graphics* graphics, const Matrix4& localTransform) +{ + this->Update(); + + const Matrix4& tm = graphics->GetTransform(); + bool is2D = tm.IsAffine2DTransform(); + const Colorf color = graphics->GetColor(); + + Matrix4 t(tm, localTransform); + + vertex::Vertex vertexData[4]; + std::fill_n(vertexData, 4, vertex::Vertex {}); + + Vector2 transformed[4]; + std::fill_n(transformed, 4, Vector2 {}); + + Vector2 positions[4]; + + std::fill_n(positions, 4, Vector2 {}); + for (size_t index = 0; index < 4; index++) + positions[index] = + Vector2(this->vertices[index].position[0], this->vertices[index].position[1]); + + if (is2D) + t.TransformXY(transformed, positions, 4); + + DkResHandle handles[3] = { this->images[0]->GetHandle(), this->images[1]->GetHandle(), + this->images[2]->GetHandle() }; + + for (size_t i = 0; i < 4; i++) + { + vertexData[i] = { { transformed[i].x, transformed[i].y, 0.0f }, + { color.r, color.g, color.b, color.a }, + { vertex::normto16t(this->vertices[i].texcoord[0]), + vertex::normto16t(this->vertices[i].texcoord[1]) } }; + } + + ::deko3d::Instance().RenderVideo(handles, vertexData, 4); +} diff --git a/source/common/delay.cpp b/source/common/delay.cpp new file mode 100644 index 000000000..6f022015b --- /dev/null +++ b/source/common/delay.cpp @@ -0,0 +1,16 @@ +#if defined(__3DS__) + #include <3ds.h> +#elif defined(__SWITCH__) + #include +#endif + +#include "common/delay.h" +#include "modules/timer/timerc.h" + +void love::Sleep(float ms) +{ + uint32_t milliseconds = static_cast(ms); + uint64_t nanoSeconds = milliseconds * common::Timer::SLEEP_DURATION; + + svcSleepThread(nanoSeconds); +} diff --git a/source/common/pixelformat.cpp b/source/common/pixelformat.cpp index 41382b90f..af03303d0 100644 --- a/source/common/pixelformat.cpp +++ b/source/common/pixelformat.cpp @@ -6,6 +6,8 @@ unsigned love::GetPixelFormatSize(PixelFormat format) { switch (format) { + case PIXELFORMAT_R8: + return 1; case PIXELFORMAT_DXT1: case PIXELFORMAT_ETC1: case PIXELFORMAT_ETC2_RGBA1: @@ -33,6 +35,8 @@ unsigned love::GetPixelFormatSize(PixelFormat format) case PIXELFORMAT_ASTC_12x10: case PIXELFORMAT_ASTC_12x12: return 1; + case PIXELFORMAT_RGB8: + return 3; default: return 0; } @@ -42,9 +46,12 @@ int love::GetPixelFormatColorComponents(PixelFormat format) { switch (format) { + case PIXELFORMAT_R8: + return 1; case PIXELFORMAT_DXT1: case PIXELFORMAT_ETC1: case PIXELFORMAT_ETC2_RGB: + case PIXELFORMAT_RGB8: return 3; case PIXELFORMAT_DXT3: case PIXELFORMAT_ETC2_RGBA1: diff --git a/source/modules/graphics/graphicsc.cpp b/source/modules/graphics/graphicsc.cpp index b1884daca..8798a1fc8 100644 --- a/source/modules/graphics/graphicsc.cpp +++ b/source/modules/graphics/graphicsc.cpp @@ -200,6 +200,11 @@ bool Graphics::IsCanvasActive(Canvas* canvas) const return target.Get() == canvas; } +Video* Graphics::NewVideo(VideoStream* stream, float dpiscale) +{ + return new Video(this, stream, dpiscale); +} + bool Graphics::IsCanvasActive() const { return this->states.back().canvas != nullptr; @@ -313,6 +318,12 @@ Vector2 Graphics::InverseTransformPoint(Vector2 point) /* Objects */ +Image* Graphics::NewImage(Texture::TextureType t, PixelFormat format, int width, int height, + int slices) +{ + return new Image(t, format, width, height, slices); +} + Image* Graphics::NewImage(const Image::Slices& data) { return new Image(data); diff --git a/source/modules/graphics/wrap_graphics.cpp b/source/modules/graphics/wrap_graphics.cpp index 5fc313e4c..c6f54772c 100644 --- a/source/modules/graphics/wrap_graphics.cpp +++ b/source/modules/graphics/wrap_graphics.cpp @@ -8,6 +8,9 @@ #include "objects/compressedimagedata/wrap_compressedimagedata.h" #include "objects/imagedata/wrap_imagedata.h" +#include "objects/video/wrap_video.h" + +#include "wrap_graphics_lua.h" using namespace love; @@ -853,6 +856,24 @@ static int _pushNewImage(lua_State* L, Image::Slices& slices) return 1; } +int Wrap_Graphics::NewVideo(lua_State* L) +{ + if (!Luax::IsType(L, 1, VideoStream::type)) + Luax::ConvertObject(L, 1, "video", "newVideoStream"); + + VideoStream* stream = Luax::CheckType(L, 1); + float dpiScale = luaL_optnumber(L, 2, 1.0f); + + Video* video = nullptr; + + Luax::CatchException(L, [&]() { video = instance()->NewVideo(stream, dpiScale); }); + + Luax::PushType(L, video); + video->Release(); + + return 1; +} + int Wrap_Graphics::NewImage(lua_State* L) { Image::Slices slices(Texture::TEXTURE_2D); @@ -1424,6 +1445,7 @@ static constexpr luaL_Reg functions[] = { "newImage", Wrap_Graphics::NewImage }, { "newQuad", Wrap_Graphics::NewQuad }, { "newText", Wrap_Graphics::NewText }, + { "_newVideo", Wrap_Graphics::NewVideo }, { "origin", Wrap_Graphics::Origin }, { "points", Wrap_Graphics::Points }, { "polygon", Wrap_Graphics::Polygon }, @@ -1476,6 +1498,7 @@ static constexpr lua_CFunction types[] = Wrap_Shader::Register, #endif Wrap_Text::Register, + Wrap_Video::Register, nullptr }; // clang-format on @@ -1503,5 +1526,9 @@ int Wrap_Graphics::Register(lua_State* L) int result = Luax::RegisterModule(L, wrappedModule); + luaL_loadbuffer(L, (const char*)wrap_graphics_lua, wrap_graphics_lua_size, "wrap_math.lua"); + lua_pushvalue(L, -2); + lua_call(L, 1, 0); + return result; } diff --git a/source/modules/love/love.cpp b/source/modules/love/love.cpp index b94795c63..b1facb44d 100644 --- a/source/modules/love/love.cpp +++ b/source/modules/love/love.cpp @@ -19,6 +19,7 @@ #include "modules/thread/wrap_threadmodule.h" #include "modules/timer/wrap_timer.h" #include "modules/touch/wrap_touch.h" +#include "modules/video/wrap_videomodule.h" #include "modules/window/wrap_window.h" #include "https/common/HTTPSCommon.h" @@ -62,6 +63,7 @@ static constexpr luaL_Reg modules[] = { "love.thread", Wrap_ThreadModule::Register }, { "love.timer", Wrap_Timer::Register }, { "love.touch", Wrap_Touch::Register }, + { "love.video", Wrap_VideoModule::Register }, { "love.window", Wrap_Window::Register }, { "love.nogame", love::NoGame }, { "love.arg", love::LoadArgs }, diff --git a/source/modules/love/scripts/boot.lua b/source/modules/love/scripts/boot.lua index d329b52ce..6a72ee446 100644 --- a/source/modules/love/scripts/boot.lua +++ b/source/modules/love/scripts/boot.lua @@ -176,7 +176,7 @@ function love.init() font = true, thread = true, window = true, - video = false, + video = true, }, audio = { mixwithsystem = true, @@ -259,6 +259,7 @@ function love.init() "image", "font", "window", + "video", "graphics", "math", "physics", diff --git a/source/modules/love/scripts/callbacks.lua b/source/modules/love/scripts/callbacks.lua index a24544068..b53e348d5 100644 --- a/source/modules/love/scripts/callbacks.lua +++ b/source/modules/love/scripts/callbacks.lua @@ -347,9 +347,11 @@ local function fix_long_error(font, text, max_width) -- note: don't forget to concat this local str = "%s\n... and %d more lines." local extra_lines = (result - max_lines) - local full_text = str:format(text_result, extra_lines) - return full_text + if extra_lines > 0 then + return str:format(text_result, extra_lines) + end + return text end function love.errorhandler(message) diff --git a/source/modules/timer/timerc.cpp b/source/modules/timer/timerc.cpp index aead329fe..d1a81d9db 100644 --- a/source/modules/timer/timerc.cpp +++ b/source/modules/timer/timerc.cpp @@ -4,8 +4,6 @@ using namespace love::common; // Löve2D Functions -static constexpr auto SLEEP_DURATION = 1000000ULL; - #if defined(__3DS__) #include <3ds.h> #elif defined(__SWITCH__) @@ -48,7 +46,7 @@ void Timer::Sleep(float seconds) if (seconds >= 0) { u32 milliseconds = seconds * 1000.0f; - u64 nanoSeconds = milliseconds * SLEEP_DURATION; + u64 nanoSeconds = milliseconds * Timer::SLEEP_DURATION; svcSleepThread(nanoSeconds); } diff --git a/source/modules/video/videomodule.cpp b/source/modules/video/videomodule.cpp new file mode 100644 index 000000000..aff66d8bd --- /dev/null +++ b/source/modules/video/videomodule.cpp @@ -0,0 +1,25 @@ +#include "modules/video/videomodule.h" +#include "modules/video/worker.h" + +using namespace love; + +#include "debug/logger.h" + +VideoModule::VideoModule() +{ + this->workerThread = new Worker(); + this->workerThread->Start(); +} + +VideoModule::~VideoModule() +{ + delete this->workerThread; +} + +VideoStream* VideoModule::NewVideoStream(File* file) +{ + TheoraStream* stream = new TheoraStream(file); + this->workerThread->AddStream(stream); + + return stream; +} diff --git a/source/modules/video/worker.cpp b/source/modules/video/worker.cpp new file mode 100644 index 000000000..d22be6d1b --- /dev/null +++ b/source/modules/video/worker.cpp @@ -0,0 +1,77 @@ +#include "modules/video/worker.h" + +#include "modules/thread/types/lock.h" +#include "objects/thread/thread.h" + +#include "common/delay.h" +#include "modules/timer/timer.h" + +using namespace love; + +Worker::Worker() : stopping(false) +{ + this->threadName = "VideoWorker"; +} + +Worker::~Worker() +{ + this->Stop(); +} + +void Worker::AddStream(TheoraStream* stream) +{ + thread::Lock lock(this->mutex); + this->streams.push_back(stream); + + this->condition->Broadcast(); +} + +void Worker::Stop() +{ + { + thread::Lock lock(this->mutex); + this->stopping = true; + this->condition->Broadcast(); + } + + this->owner->Wait(); +} + +void Worker::ThreadFunction() +{ + double lastFrame = Timer::GetTime(); + + while (true) + { + love::Sleep(2); + + thread::Lock lock(this->mutex); + + while (!this->stopping && this->streams.empty()) + { + this->condition->Wait(this->mutex); + lastFrame = Timer::GetTime(); + } + + if (this->stopping) + return; + + double currentFrame = Timer::GetTime(); + double delta = currentFrame - lastFrame; + + lastFrame = currentFrame; + + for (auto it = this->streams.begin(); it != this->streams.end(); ++it) + { + TheoraStream* stream = *it; + + if (stream->GetReferenceCount() == 1) + { + this->streams.erase(it); + break; + } + + stream->ThreadedFillBackBuffer(delta); + } + } +} diff --git a/source/modules/video/wrap_videomodule.cpp b/source/modules/video/wrap_videomodule.cpp new file mode 100644 index 000000000..8cd2c423f --- /dev/null +++ b/source/modules/video/wrap_videomodule.cpp @@ -0,0 +1,63 @@ +#include "modules/video/wrap_videomodule.h" +#include "objects/videostream/wrap_videostream.h" + +#include "modules/filesystem/wrap_filesystem.h" +#include "objects/file/file.h" +#include "objects/videostream/videostream.h" + +using namespace love; + +#define instance() (Module::GetInstance(Module::M_VIDEO)) + +int Wrap_VideoModule::NewVideoStream(lua_State* L) +{ + File* file = Wrap_Filesystem::GetFile(L, 1); + + VideoStream* stream = nullptr; + Luax::CatchException(L, [&]() { + if (!file->IsOpen() && !file->Open(File::MODE_READ)) + luaL_error(L, "File is not open and canot be opened."); + + stream = instance()->NewVideoStream(file); + }); + + Luax::PushType(L, stream); + + stream->Release(); + file->Release(); + + return 1; +} + +// clang-format off +static constexpr lua_CFunction types[] = +{ + Wrap_VideoStream::Register, + 0 +}; + +static constexpr luaL_Reg functions[] = +{ + { "newVideoStream", Wrap_VideoModule::NewVideoStream }, + { 0 , 0 } +}; +// clang-format on + +int Wrap_VideoModule::Register(lua_State* L) +{ + VideoModule* instance = instance(); + + if (instance == nullptr) + Luax::CatchException(L, [&]() { instance = new VideoModule(); }); + else + instance->Retain(); + + WrappedModule module; + module.instance = instance; + module.name = "video"; + module.type = &Module::type; + module.functions = functions; + module.types = types; + + return Luax::RegisterModule(L, module); +} diff --git a/source/objects/data/byte/bytedata.cpp b/source/objects/data/bytedata/bytedata.cpp similarity index 100% rename from source/objects/data/byte/bytedata.cpp rename to source/objects/data/bytedata/bytedata.cpp diff --git a/source/objects/data/byte/wrap_bytedata.cpp b/source/objects/data/bytedata/wrap_bytedata.cpp similarity index 100% rename from source/objects/data/byte/wrap_bytedata.cpp rename to source/objects/data/bytedata/wrap_bytedata.cpp diff --git a/source/objects/data/compressed/compresseddata.cpp b/source/objects/data/compresseddata/compresseddata.cpp similarity index 100% rename from source/objects/data/compressed/compresseddata.cpp rename to source/objects/data/compresseddata/compresseddata.cpp diff --git a/source/objects/data/compressed/wrap_compresseddata.cpp b/source/objects/data/compresseddata/wrap_compresseddata.cpp similarity index 100% rename from source/objects/data/compressed/wrap_compresseddata.cpp rename to source/objects/data/compresseddata/wrap_compresseddata.cpp diff --git a/source/objects/data/view/dataview.cpp b/source/objects/data/dataview/dataview.cpp similarity index 100% rename from source/objects/data/view/dataview.cpp rename to source/objects/data/dataview/dataview.cpp diff --git a/source/objects/data/view/wrap_dataview.cpp b/source/objects/data/dataview/wrap_dataview.cpp similarity index 100% rename from source/objects/data/view/wrap_dataview.cpp rename to source/objects/data/dataview/wrap_dataview.cpp diff --git a/source/objects/imagedata/imagedata.cpp b/source/objects/imagedata/imagedata.cpp index f1b892d33..c31df600f 100644 --- a/source/objects/imagedata/imagedata.cpp +++ b/source/objects/imagedata/imagedata.cpp @@ -150,8 +150,8 @@ void ImageData::Decode(Data* data) this->width = decoded.width; this->height = decoded.height; #else - this->width = decoded.subWidth; - this->height = decoded.subHeight; + this->width = decoded.subWidth; + this->height = decoded.subHeight; #endif this->data = decoded.data; @@ -253,7 +253,7 @@ static float clamp01(float x) // pixel->rgba8[0] = static_cast(clamp01(color.r) * 0xFF + 0.5f); // pixel->rgba8[1] = static_cast(clamp01(color.g) * 0xFF + 0.5f); // } - +#if defined(__SWITCH__) static void setPixelRGBA8(const Colorf& color, ImageData::Pixel* pixel) { pixel->rgba8[0] = static_cast(clamp01(color.r) * 0xFF); @@ -261,16 +261,8 @@ static void setPixelRGBA8(const Colorf& color, ImageData::Pixel* pixel) pixel->rgba8[2] = static_cast(clamp01(color.b) * 0xFF); pixel->rgba8[3] = static_cast(clamp01(color.a) * 0xFF); } - -static void setPixelRGBA16(const Colorf& color, ImageData::Pixel* pixel) -{ - pixel->rgba16[0] = static_cast(clamp01(color.r) * 0xFFFF + 0.5f); - pixel->rgba16[1] = static_cast(clamp01(color.b) * 0xFFFF + 0.5f); - pixel->rgba16[2] = static_cast(clamp01(color.g) * 0xFFFF + 0.5f); - pixel->rgba16[3] = static_cast(clamp01(color.a) * 0xFFFF + 0.5f); -} - -static void setPixelTex3ds(const Colorf& color, ImageData::Pixel* pixel) +#elif defined(__3DS__) +static void setPixelRGBA8(const Colorf& color, ImageData::Pixel* pixel) { uint8_t r = uint8_t(0xFF * clamp01(color.r) + 0.5f); uint8_t g = uint8_t(0xFF * clamp01(color.g) + 0.5f); @@ -279,7 +271,17 @@ static void setPixelTex3ds(const Colorf& color, ImageData::Pixel* pixel) pixel->packed32 = (a | (b << uint32_t(0x08)) | (g << uint32_t(0x10)) | (r << uint32_t(0x18))); } +#endif + +static void setPixelRGBA16(const Colorf& color, ImageData::Pixel* pixel) +{ + pixel->rgba16[0] = static_cast(clamp01(color.r) * 0xFFFF + 0.5f); + pixel->rgba16[1] = static_cast(clamp01(color.b) * 0xFFFF + 0.5f); + pixel->rgba16[2] = static_cast(clamp01(color.g) * 0xFFFF + 0.5f); + pixel->rgba16[3] = static_cast(clamp01(color.a) * 0xFFFF + 0.5f); +} +#if defined(__SWITCH__) static void getPixelRGBA8(const ImageData::Pixel* pixel, Colorf& color) { color.r = pixel->rgba8[0] / 0xFF; @@ -287,6 +289,15 @@ static void getPixelRGBA8(const ImageData::Pixel* pixel, Colorf& color) color.b = pixel->rgba8[2] / 0xFF; color.a = pixel->rgba8[3] / 0xFF; } +#elif defined(__3DS__) +static void getPixelRGBA8(const ImageData::Pixel* pixel, Colorf& color) +{ + color.r = ((pixel->packed32 & 0xFF000000) >> 0x18) / 255.0f; + color.g = ((pixel->packed32 & 0x00FF0000) >> 0x10) / 255.0f; + color.b = ((pixel->packed32 & 0x0000FF00) >> 0x08) / 255.0f; + color.a = ((pixel->packed32 & 0x000000FF) >> 0x00) / 255.0f; +} +#endif static void getPixelRGBA16(const ImageData::Pixel* pixel, Colorf& color) { @@ -296,14 +307,6 @@ static void getPixelRGBA16(const ImageData::Pixel* pixel, Colorf& color) color.a = pixel->rgba16[3] / 0xFFFF; } -static void getPixelTex3ds(const ImageData::Pixel* pixel, Colorf& color) -{ - color.r = ((pixel->packed32 & 0xFF000000) >> 0x18) / 255.0f; - color.g = ((pixel->packed32 & 0x00FF0000) >> 0x10) / 255.0f; - color.b = ((pixel->packed32 & 0x0000FF00) >> 0x08) / 255.0f; - color.a = ((pixel->packed32 & 0x000000FF) >> 0x00) / 255.0f; -} - void ImageData::SetPixel(int x, int y, const Colorf& color) { if (!this->Inside(x, y)) @@ -320,12 +323,12 @@ void ImageData::SetPixel(int x, int y, const Colorf& color) this->pixelSetFunction(color, pixel); #else unsigned _width = NextPO2(this->width + 2); - unsigned index = coordToIndex(_width, x + 1, y + 1); + unsigned index = coordToIndex(_width, x + 1, y + 1); Pixel* pixel = reinterpret_cast((uint32_t*)this->data + index); Lock lock(this->mutex); - setPixelTex3ds(color, pixel); + setPixelRGBA8(color, pixel); #endif } @@ -345,12 +348,12 @@ void ImageData::GetPixel(int x, int y, Colorf& color) const this->pixelGetFunction(pixel, color); #else unsigned _width = NextPO2(this->width + 2); - unsigned index = coordToIndex(_width, x + 1, y + 1); + unsigned index = coordToIndex(_width, x + 1, y + 1); const Pixel* pixel = reinterpret_cast((uint32_t*)this->data + index); Lock lock(this->mutex); - getPixelTex3ds(pixel, color); + getPixelRGBA8(pixel, color); #endif } @@ -387,18 +390,21 @@ void ImageData::Paste(ImageData* src, int dx, int dy, int sx, int sy, int sw, in sx -= dx; dx = 0; } + if (dy < 0) { sh += dy; sy -= dy; dy = 0; } + if (sx < 0) { sw += sx; dx -= sx; sx = 0; } + if (sy < 0) { sh += sy; @@ -435,14 +441,14 @@ void ImageData::Paste(ImageData* src, int dx, int dy, int sx, int sy, int sw, in Colorf color {}; const Pixel* srcPixel = reinterpret_cast((uint32_t*)src->data + srcIndex); - getPixelTex3ds(srcPixel, color); + getPixelRGBA8(srcPixel, color); Pixel* dstPixel = reinterpret_cast((uint32_t*)this->data + dstIndex); - setPixelTex3ds(color, dstPixel); + setPixelRGBA8(color, dstPixel); } } #elif defined(__SWITCH__) - uint8_t* source = (uint8_t*)src->GetData(); + uint8_t* source = (uint8_t*)src->GetData(); uint8_t* destination = (uint8_t*)this->GetData(); size_t srcpixelsize = src->GetPixelSize(); @@ -489,7 +495,6 @@ ImageData::PixelSetFunction ImageData::GetPixelSetFunction(PixelFormat format) switch (format) { case PIXELFORMAT_TEX3DS_RGBA8: - return setPixelTex3ds; case PIXELFORMAT_RGBA8: return setPixelRGBA8; case PIXELFORMAT_RGBA16: @@ -504,7 +509,6 @@ ImageData::PixelGetFunction ImageData::GetPixelGetFunction(PixelFormat format) switch (format) { case PIXELFORMAT_TEX3DS_RGBA8: - return getPixelTex3ds; case PIXELFORMAT_RGBA8: return getPixelRGBA8; case PIXELFORMAT_RGBA16: diff --git a/source/objects/random/randomgenerator.cpp b/source/objects/randomgenerator/randomgenerator.cpp similarity index 100% rename from source/objects/random/randomgenerator.cpp rename to source/objects/randomgenerator/randomgenerator.cpp diff --git a/source/objects/random/wrap_randomgenerator.cpp b/source/objects/randomgenerator/wrap_randomgenerator.cpp similarity index 100% rename from source/objects/random/wrap_randomgenerator.cpp rename to source/objects/randomgenerator/wrap_randomgenerator.cpp diff --git a/source/objects/video/videoc.cpp b/source/objects/video/videoc.cpp new file mode 100644 index 000000000..9eab61be7 --- /dev/null +++ b/source/objects/video/videoc.cpp @@ -0,0 +1,69 @@ +#include "objects/video/videoc.h" + +using namespace love::common; + +love::Type Video::type("Video", &Drawable::type); + +Video::Video(Graphics* graphics, VideoStream* stream, float dpiScale) : + stream(stream), + width(stream->GetWidth() / dpiScale), + height(stream->GetHeight() / dpiScale), + filter(Texture::defaultFilter) +{ + this->filter.mipmap = Texture::FILTER_NONE; + this->stream->FillBackBuffer(); +} + +Video::~Video() +{ + if (this->source) + this->source->Stop(); +} + +love::VideoStream* Video::GetStream() +{ + return this->stream; +} + +love::Source* Video::GetSource() +{ + return this->source; +} + +void Video::SetSource(love::Source* source) +{ + this->source = source; +} + +int Video::GetWidth() const +{ + return this->width; +} + +int Video::GetHeight() const +{ + return this->height; +} + +int Video::GetPixelWidth() const +{ + return this->stream->GetWidth(); +} + +int Video::GetPixelHeight() const +{ + return this->stream->GetHeight(); +} + +void Video::SetFilter(const Texture::Filter& filter) +{ + for (const auto& image : this->images) + image->SetFilter(filter); + + this->filter = filter; +} + +const Texture::Filter& Video::GetFilter() const +{ + return this->filter; +} diff --git a/source/objects/video/wrap_video.cpp b/source/objects/video/wrap_video.cpp new file mode 100644 index 000000000..4ca859f16 --- /dev/null +++ b/source/objects/video/wrap_video.cpp @@ -0,0 +1,173 @@ +#include "objects/video/wrap_video.h" +#include "wrap_video_lua.h" + +using namespace love; + +int Wrap_Video::GetStream(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + Luax::PushType(L, self->GetStream()); + + return 1; +} + +int Wrap_Video::GetSource(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + Source* source = self->GetSource(); + + if (source) + Luax::PushType(L, source); + else + lua_pushnil(L); + + return 1; +} + +int Wrap_Video::SetSource(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + if (lua_isnoneornil(L, 2)) + self->SetSource(nullptr); + else + { + Source* source = Luax::CheckType(L, 2); + self->SetSource(source); + } + + return 0; +} + +int Wrap_Video::GetWidth(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + lua_pushnumber(L, self->GetWidth()); + + return 1; +} + +int Wrap_Video::GetHeight(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + lua_pushnumber(L, self->GetHeight()); + + return 1; +} + +int Wrap_Video::GetDimensions(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + lua_pushnumber(L, self->GetWidth()); + lua_pushnumber(L, self->GetHeight()); + + return 2; +} + +int Wrap_Video::GetPixelWidth(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + lua_pushnumber(L, self->GetPixelWidth()); + + return 1; +} + +int Wrap_Video::GetPixelHeight(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + lua_pushnumber(L, self->GetPixelHeight()); + + return 1; +} + +int Wrap_Video::GetPixelDimensions(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + + lua_pushnumber(L, self->GetPixelWidth()); + lua_pushnumber(L, self->GetPixelHeight()); + + return 2; +} + +int Wrap_Video::SetFilter(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + Texture::Filter filter = self->GetFilter(); + + const char* min = luaL_checkstring(L, 2); + const char* mag = luaL_checkstring(L, 3); + + if (!Texture::GetConstant(min, filter.min)) + return Luax::EnumError(L, "filter mode", Texture::GetConstants(filter.min), min); + + if (!Texture::GetConstant(mag, filter.mag)) + return Luax::EnumError(L, "filter mode", Texture::GetConstants(filter.mag), mag); + + filter.anisotropy = luaL_optnumber(L, 4, 1.0f); + + Luax::CatchException(L, [&]() { self->SetFilter(filter); }); + + return 0; +} + +int Wrap_Video::GetFilter(lua_State* L) +{ + Video* self = Wrap_Video::CheckVideo(L, 1); + const Texture::Filter filter = self->GetFilter(); + + const char* min = nullptr; + const char* mag = nullptr; + + if (!Texture::GetConstant(filter.min, min)) + return luaL_error(L, "Unknown filter mode."); + + if (!Texture::GetConstant(filter.mag, mag)) + return luaL_error(L, "Unknown filter mode."); + + lua_pushstring(L, min); + lua_pushstring(L, mag); + + lua_pushnumber(L, filter.anisotropy); + + return 3; +} + +Video* Wrap_Video::CheckVideo(lua_State* L, int index) +{ + return Luax::CheckType