diff --git a/Compositor/lib/Mesa/Compositor.cpp b/Compositor/lib/Mesa/Compositor.cpp index 6e9fa8b53..1748c3e1b 100644 --- a/Compositor/lib/Mesa/Compositor.cpp +++ b/Compositor/lib/Mesa/Compositor.cpp @@ -37,6 +37,8 @@ #include #include +#include +#include MODULE_NAME_DECLARATION(BUILD_REFERENCE) @@ -44,6 +46,7 @@ namespace Thunder { namespace Plugin { const Compositor::Color pink = { 1.0f, 0.411f, 0.705f, 1.0f }; const Compositor::Color black = { 0.f, 0.f, 0.f, 1.0f }; + constexpr char DefaultRenderNode[] = "/dev/dri/renderD128"; class CompositorImplementation : public Exchange::IComposition, @@ -60,7 +63,7 @@ namespace Plugin { Config() : Core::JSON::Container() - , Render() + , Render(DefaultRenderNode) , Resolution(Exchange::IDeviceVideoCapabilities::ScreenResolution::ScreenResolution_Unknown) // Auto-detect , Format(DRM_FORMAT_INVALID) // auto select , Modifier(DRM_FORMAT_MOD_INVALID) // auto select @@ -138,7 +141,7 @@ namespace Plugin { : _parent(parent) { } - ~PrivilegedRequestCallback() override = default; + ~PrivilegedRequestCallback() override { } public: void Request(const uint32_t id, Core::PrivilegedRequest::Container& descriptors) override @@ -171,48 +174,151 @@ namespace Plugin { public: constexpr static uint8_t InvalidBufferId = uint8_t(~0); + constexpr static uint32_t MaxClientBuffers = 4; + struct BufferStats { + std::atomic requestCount; // Times requested + std::atomic renderCount; // Times rendered + std::atomic skipCount; // Times skipped + std::atomic totalAge; // Sum of ages (µs) + std::atomic maxAge; // Max age seen (µs) + std::atomic lastUsed; // Timestamp last used + + BufferStats() + : requestCount(0) + , renderCount(0) + , skipCount(0) + , totalAge(0) + , maxAge(0) + , lastUsed(0) + { + } + + uint64_t AvgAge() const + { + uint64_t count = renderCount.load(); + return count > 0 ? totalAge.load() / count : 0; + } + }; + struct Stats { + // Overall stats std::atomic renderCount; std::atomic totalRenderTime; std::atomic skipCount; + // Per-buffer detailed stats + std::array buffers; + + // Queue stats + std::atomic queueFullCount; + std::atomic queueMaxSize; // Max queue depth seen + + // Current state + std::atomic currentBufferId; + std::atomic lastRenderTime; + Stats() : renderCount(0) , totalRenderTime(0) , skipCount(0) + , buffers {} // Zero-init array + , queueFullCount(0) + , queueMaxSize(0) + , currentBufferId(0xFF) // InvalidBufferId + , lastRenderTime(0) { } + + string ToString() const + { + std::ostringstream ss; + + uint64_t renders = renderCount.load(); + uint64_t skips = skipCount.load(); + uint64_t total = renders + skips; + ss << "Overall:\n"; + ss << " Renders: " << renders << "\n"; + ss << " Skips: " << skips; + if (total > 0) { + ss << " (" << std::fixed << std::setprecision(1) << (100.0 * skips / total) << "%)\n"; + } else { + ss << "\n"; + } + + if (renders > 0) { + uint64_t avgTime = totalRenderTime.load() / renders; + ss << " Avg Time: " << avgTime << " µs\n"; + } + + ss << "Queue:\n"; + ss << " Full Events: " << queueFullCount.load() << "\n"; + ss << " Max Depth: " << queueMaxSize.load() << "\n"; + + ss << "Buffers:\n"; + for (size_t i = 0; i < MaxClientBuffers; i++) { + const auto& buf = buffers[i]; + uint64_t requests = buf.requestCount.load(); + uint64_t bufRenders = buf.renderCount.load(); + uint64_t bufSkips = buf.skipCount.load(); + + if (requests > 0 || bufRenders > 0) { + ss << " [" << i << "] Req:" << requests + << " Rnd:" << bufRenders + << " Skip:" << bufSkips; + + if (bufRenders > 0) { + ss << " AvgAge:" << buf.AvgAge() + << " MaxAge:" << buf.maxAge.load() << " µs"; + } + ss << "\n"; + } + } + + uint8_t current = currentBufferId.load(); + if (current < MaxClientBuffers) { + uint64_t now = Core::Time::Now().Ticks(); + uint64_t lastRender = lastRenderTime.load(); + uint64_t idleTime = lastRender > 0 ? (now - lastRender) : 0; + ss << "Current: Buffer " << static_cast(current) + << " (idle " << idleTime << " µs / " + << std::fixed << std::setprecision(1) << (idleTime / 1000.0) << " ms)\n"; + } + + return ss.str(); + } }; private: - constexpr static uint32_t MAX_BUFFERS = 4; - - class Buffer : public Graphics::ServerBufferType<1> { + class ExternalBuffer : public Graphics::ServerBufferType<1> { using BaseClass = Graphics::ServerBufferType<1>; public: - Buffer() = delete; - Buffer(Buffer&&) = delete; - Buffer(const Buffer&) = delete; - Buffer& operator=(Buffer&&) = delete; - Buffer& operator=(const Buffer&) = delete; + ExternalBuffer() = delete; + ExternalBuffer(ExternalBuffer&&) = delete; + ExternalBuffer(const ExternalBuffer&) = delete; + ExternalBuffer& operator=(ExternalBuffer&&) = delete; + ExternalBuffer& operator=(const ExternalBuffer&) = delete; - Buffer(Client& client, Core::PrivilegedRequest::Container& descriptors) + ExternalBuffer(Client& client, Core::PrivilegedRequest::Container& descriptors) : BaseClass() , _client(client) , _id(InvalidBufferId) , _texture() { Load(descriptors); - TRACE(Trace::Information, (_T("Buffer for %s[%p] %dx%d, format=0x%08" PRIX32 ", modifier=0x%016" PRIX64), client.Name().c_str(), this, BaseClass::Height(), BaseClass::Width(), BaseClass::Format(), BaseClass::Modifier())); + TRACE(Trace::Information, (_T("ExternalBuffer for %s[%p] %dx%d, format=0x%08" PRIX32 ", modifier=0x%016" PRIX64), client.Name().c_str(), this, BaseClass::Height(), BaseClass::Width(), BaseClass::Format(), BaseClass::Modifier())); Core::ResourceMonitor::Instance().Register(*this); } - ~Buffer() override + ~ExternalBuffer() override { Core::ResourceMonitor::Instance().Unregister(*this); _texture.Release(); - TRACE(Trace::Information, (_T("Buffer for %s[%p] destructed"), _client.Name().c_str(), this)); + + Rendered(); + Published(); + + TRACE(Trace::Information, (_T("ExternalBuffer for %s[%p] destructed"), _client.Name().c_str(), this)); } void Request() override @@ -232,14 +338,14 @@ namespace Plugin { void Configure(const uint8_t id, const Core::ProxyType& texture) { - ASSERT(id < MAX_BUFFERS); + ASSERT(id < MaxClientBuffers); ASSERT(_id == InvalidBufferId); ASSERT(_texture.IsValid() == false); _id = id; _texture = texture; - TRACE(Trace::Information, (_T("Buffer for %s[%p] configured as id:%d"), _client.Name().c_str(), this, _id)); + TRACE(Trace::Information, (_T("ExternalBuffer for %s[%p] configured as id:%d"), _client.Name().c_str(), this, _id)); } private: @@ -261,7 +367,7 @@ namespace Plugin { , _client(client) { } - ~Remote() override = default; + ~Remote() override { } public: // Core::IUnknown @@ -280,12 +386,18 @@ namespace Plugin { uint32_t Release() const override { + uint32_t result = Core::ERROR_NONE; + if (Core::InterlockedDecrement(_refCount) == 0) { // Time to say goodby, all remote clients died.. + _client.AddRef(); const_cast(_client).Revoke(); - return (Core::ERROR_DESTRUCTION_SUCCEEDED); + _client.Release(); + + result = Core::ERROR_DESTRUCTION_SUCCEEDED; } - return Core::ERROR_NONE; + + return result; } // Exchange::IComposition::IClient methods @@ -331,12 +443,13 @@ namespace Plugin { class AtomicFifo { public: AtomicFifo() - : _head{ 0 } - , _tail{ 0 } + : _queue() + , _head(0) + , _tail(0) { // Initialize array elements for (auto& element : _queue) { - element.store(T{}, std::memory_order_relaxed); + element.store(T {}, std::memory_order_relaxed); } } @@ -432,19 +545,28 @@ namespace Plugin { , _geometry({ 0, 0, width, height }) // rendering geometry , _remoteClient(*this) , _geometryChanged(false) - , _buffers(MAX_BUFFERS) - , _pendingQueue() + , _buffers(MaxClientBuffers) + , _requestQueue() , _currentId(InvalidBufferId) , _renderingId(InvalidBufferId) - + , _pendingId(InvalidBufferId) + , _activeId(InvalidBufferId) + , _retiredId(InvalidBufferId) + , _stats() { TRACE(Trace::Information, (_T("Client Constructed %s[%p] id:%d %dx%d"), _callsign.c_str(), this, _id, _width, _height)); } ~Client() override { - _buffers.Clear(); - - _parent.Render(); // request a render to remove this surface from the composition. + if (_buffers.Count() > 0) { + for (unsigned int i = 0; i < _buffers.Count(); i++) { + Core::ProxyType texture = _buffers[i]->Texture(); + if (texture.IsValid()) { + _parent.DestroyTexture(texture); + } + } + _buffers.Clear(); + } TRACE(Trace::Information, (_T("Client %s[%p] destroyed"), _callsign.c_str(), this)); } @@ -460,60 +582,176 @@ namespace Plugin { void Activate(const uint8_t id) { if (id < _buffers.Count()) { + _stats.buffers[id].requestCount.fetch_add(1, std::memory_order_relaxed); + _stats.buffers[id].lastUsed.store(Core::Time::Now().Ticks(), std::memory_order_relaxed); + _currentId.store(id, std::memory_order_release); - _parent.Render(); + + // Signal new frame via queue + bool queued = _requestQueue.Push(id, false); + + if (queued) { + _parent.Render(); + } else { + TRACE(Trace::Error, (_T("Client %s request queue full, dropping buffer %d"), _callsign.c_str(), id)); + // Note: _currentId is still updated, so next vsync will render this buffer + _stats.queueFullCount.fetch_add(1, std::memory_order_relaxed); + } } } public: - Core::ProxyType Acquire() + uint8_t CurrentBufferId() const + { + return _stats.currentBufferId.load(std::memory_order_relaxed); + } + + void IncrementBufferSkip(uint8_t id) + { + if (id < MaxClientBuffers) { + _stats.buffers[id].skipCount.fetch_add(1, std::memory_order_relaxed); + } + } + + const Stats& Statistics() const + { + return _stats; + } + + Core::ProxyType Acquire(bool& isNewFrame) { Core::ProxyType texture; + isNewFrame = false; - uint8_t id = _currentId.exchange(InvalidBufferId, std::memory_order_acq_rel); + uint8_t id; + uint64_t acquireTime = Core::Time::Now().Ticks(); - if (id != InvalidBufferId && _buffers[id].IsValid()) { + // Check for new frame in queue + if (_requestQueue.Pop(id)) { + // New frame requested! Update current + _currentId.store(id, std::memory_order_release); + isNewFrame = true; + } else { + // No new frame, use current buffer (static content) + id = _currentId.load(std::memory_order_acquire); + } + + // Render the buffer (new or repeated) + if (id != InvalidBufferId && id < _buffers.Count() && _buffers[id].IsValid()) { _renderingId.store(id, std::memory_order_release); - _buffers[id]->Acquire(Compositor::DefaultTimeoutMs); - texture = _buffers[id]->Texture(); + + // Record buffer render + _stats.buffers[id].renderCount.fetch_add(1, std::memory_order_relaxed); + + // Calculate and record age + uint64_t requestTime = _stats.buffers[id].lastUsed.load(std::memory_order_relaxed); + if (requestTime > 0 && acquireTime >= requestTime) { + uint64_t age = acquireTime - requestTime; + _stats.buffers[id].totalAge.fetch_add(age, std::memory_order_relaxed); + + // Update max age using CAS + uint64_t currentMax = _stats.buffers[id].maxAge.load(std::memory_order_relaxed); + while (age > currentMax && !_stats.buffers[id].maxAge.compare_exchange_weak(currentMax, age, std::memory_order_relaxed)) + ; + } + + // Update current buffer tracking + _stats.currentBufferId.store(id, std::memory_order_relaxed); + _stats.lastRenderTime.store(acquireTime, std::memory_order_relaxed); + + if (_buffers[id]->Acquire(0) != nullptr) { + texture = _buffers[id]->Texture(); + } else { + TRACE(Trace::Error, (_T("Client %s buffer %d failed to acquire for rendering"), _callsign.c_str(), id)); + } } else { _renderingId.store(InvalidBufferId, std::memory_order_release); + + if (id != InvalidBufferId) { + TRACE(Trace::Error, (_T("Client %s invalid buffer %d"), _callsign.c_str(), id)); + } } return texture; } - void Relinquish() + // ------------------------------------------------------------------------- + // Called during RenderClient - releases texture reference + // Does NOT signal client - stores buffer as pending for later + // Only stores if this was a NEW frame (not re-rendering current) + // ------------------------------------------------------------------------- + void Relinquish(bool newFrame) { uint8_t id = _renderingId.exchange(InvalidBufferId, std::memory_order_acq_rel); - if (id != InvalidBufferId) { + if (id != InvalidBufferId && id < _buffers.Count() && _buffers[id].IsValid()) { _buffers[id]->Relinquish(); - bool queued = _pendingQueue.Push(id, false); - - if (!queued) { - TRACE(Trace::Error, (_T("Queue full! Buffer %d rejected"), id)); + // Only mark as pending if this was a NEW frame submission + // Re-rendered static content should not trigger state changes + if (newFrame) { + _pendingId.store(id, std::memory_order_release); } + } + } - ASSERT(queued == true); + // ------------------------------------------------------------------------- + // Called AFTER renderer->Finish() - GPU is done + // Promotes pending → active, active → retired + // Signals Rendered to client + // ------------------------------------------------------------------------- + void SignalRendered() + { + uint8_t id = _pendingId.exchange(InvalidBufferId, std::memory_order_acq_rel); + + // Only process if there's actually a pending buffer + if (id == InvalidBufferId) { + return; // No new frame was submitted, nothing to signal + } - if (_buffers[id].IsValid()) { - _buffers[id]->Rendered(); + if (id < _buffers.Count() && _buffers[id].IsValid()) { + // Get current active (will become retired) + uint8_t oldActive = _activeId.exchange(id, std::memory_order_acq_rel); + + // Only retire if there WAS an active buffer AND it's different from new one + // AND it's not already retired (or invalid) + if (oldActive != InvalidBufferId && oldActive != id) { + uint8_t currentRetired = _retiredId.load(std::memory_order_acquire); + + if (currentRetired == InvalidBufferId) { + // No retired buffer, safe to retire oldActive + _retiredId.store(oldActive, std::memory_order_release); + } else { + // Already have a retired buffer waiting - this shouldn't happen + // but if it does, force-publish the old retired one + if (_buffers[currentRetired].IsValid()) { + _buffers[currentRetired]->Published(); + } + + _retiredId.store(oldActive, std::memory_order_release); + } } - } else { - TRACE(Trace::Error, (_T("Can't release invalid buffers"))); + + // Signal client - safe to render next frame + _buffers[id]->Rendered(); } } - void Completed() + // ------------------------------------------------------------------------- + // Called on VSync - scanout complete + // Signals Published for retired buffer + // ------------------------------------------------------------------------- + void SignalPublished() { - uint8_t bufferId; + uint8_t id = _retiredId.exchange(InvalidBufferId, std::memory_order_acq_rel); - if (_pendingQueue.Pop(bufferId)) { - if (_buffers[bufferId].IsValid()) { - _buffers[bufferId]->Published(); - } + if (id != InvalidBufferId && id < _buffers.Count() && _buffers[id].IsValid()) { + // Signal client - safe to release buffer back to GBM + _buffers[id]->Published(); + + // TRACE(Trace::Information, + // (_T("Client %s: buffer %d Published signaled (can release to GBM)"), + // _callsign.c_str(), id)); } } @@ -578,21 +816,34 @@ namespace Plugin { } void Revoke() { - _pendingQueue.Clear(); + // Clear any pending state + _pendingId.store(InvalidBufferId, std::memory_order_release); + _activeId.store(InvalidBufferId, std::memory_order_release); + _retiredId.store(InvalidBufferId, std::memory_order_release); + + // Clean up textures immediately + for (unsigned int i = 0; i < _buffers.Count(); i++) { + Core::ProxyType texture = _buffers[i]->Texture(); + _parent.DestroyTexture(texture); + } + + _buffers.Clear(); _parent.Revoke(this); + + _parent.Render(); // Request a render to remove this surface from the composition } uint32_t AttachBuffer(Core::PrivilegedRequest::Container& descriptors) { uint32_t result = Core::ERROR_UNAVAILABLE; - if (_buffers.Count() < MAX_BUFFERS) { - Core::ProxyType buffer = Core::ProxyType::Create(*this, descriptors); + if (_buffers.Count() < MaxClientBuffers) { + Core::ProxyType buffer = Core::ProxyType::Create(*this, descriptors); if (buffer.IsValid() == true) { int index = _buffers.Add(buffer); - buffer->Configure(index, _parent.Texture(Core::ProxyType(buffer))); + buffer->Configure(index, _parent.CreateTexture(Core::ProxyType(buffer))); result = Core::ERROR_NONE; } else { result = Core::ERROR_GENERAL; @@ -615,10 +866,17 @@ namespace Plugin { Exchange::IComposition::Rectangle _geometry; // the actual geometry of the surface on the composition Remote _remoteClient; bool _geometryChanged; - Core::ProxyList _buffers; // the actual pixel buffers that are used by this client but are owed by the compositorclient. - AtomicFifo _pendingQueue; - std::atomic _currentId; - std::atomic _renderingId; + Core::ProxyList _buffers; // the actual pixel buffers that are used by this client but are owed by the compositorclient. + AtomicFifo _requestQueue; + + // Buffer state tracking - single buffer per state, no queues needed + std::atomic _currentId; // Last known buffer from client + std::atomic _renderingId; // Temp: being composed this frame + std::atomic _pendingId; // Waiting for Rendered (after Finish) + std::atomic _activeId; // On screen (waiting for next frame) + std::atomic _retiredId; // Waiting for Published (after VSync) + + Stats _stats; static uint32_t _sequence; }; // class Client @@ -643,7 +901,7 @@ namespace Plugin { } } - virtual ~Output() + ~Output() { _connector.Release(); } @@ -661,7 +919,7 @@ namespace Plugin { : _parent(parent) { } - ~Sink() override = default; + ~Sink() override { } virtual void Presented(const Compositor::IOutput* output, const uint64_t sequence, const uint64_t time) override { @@ -716,13 +974,38 @@ namespace Plugin { Core::ProxyType _connector; }; + class DetachedNotificationJob : public Core::IDispatch { + public: + DetachedNotificationJob(Exchange::IComposition::INotification* observer, const string& clientName) + : _observer(observer) + , _clientName(clientName) + { + _observer->AddRef(); + } + + ~DetachedNotificationJob() override + { + _observer->Release(); + } + + void Dispatch() override + { + _observer->Detached(_clientName); + } + + private: + Exchange::IComposition::INotification* _observer; + string _clientName; + }; + using Clients = Core::ProxyMapType; using Observers = std::vector; void Stop() { - _present.Stop(); // This should stop the rendering loop - _present.Wait(Core::Thread::STOPPED, 1000); // Wait for it to actually stop + _terminated.store(true, std::memory_order_release); + + _renderPending.store(false, std::memory_order_release); { std::lock_guard lock(_commitMutex); @@ -731,10 +1014,17 @@ namespace Plugin { _commitCV.notify_all(); // Wake up all waiting threads + _present.Stop(); // This should stop the rendering loop + + if (_present.Wait(Core::Thread::STOPPED, 5000) == false) { + TRACE(Trace::Error, ("Presenter thread did not stop in time!")); + } + _descriptorExchange.Close(); { std::lock_guard lock(_clientLock); + _clients.Clear(); } @@ -770,7 +1060,7 @@ namespace Plugin { , _engine() , _dispatcher(nullptr) , _renderDescriptor(Compositor::InvalidFileDescriptor) - , _renderNode() + , _renderNode(DefaultRenderNode) , _present(*this) , _autoScale(true) , _commitMutex() @@ -779,6 +1069,8 @@ namespace Plugin { , _totalRenderTime(0) , _frameCount(0) , _frameTime(0) + , _renderPending(false) + , _terminated(false) { } ~CompositorImplementation() override @@ -842,14 +1134,15 @@ namespace Plugin { } if ((config.Render.IsSet() == true) && (config.Render.Value().empty() == false)) { - _renderDescriptor = ::open(config.Render.Value().c_str(), O_RDWR | O_CLOEXEC); - } else { - TRACE(Trace::Error, (_T("Render node is not set in the configuration"))); - return Core::ERROR_INCOMPLETE_CONFIG; + _renderNode = config.Render.Value(); } + TRACE(Trace::Error, (_T("Selected render node %s"), _renderNode.c_str())); + + _renderDescriptor = ::open(_renderNode.c_str(), O_RDWR | O_CLOEXEC); + if (_renderDescriptor < 0) { - TRACE(Trace::Error, (_T("Failed to open render node %s, error %d"), config.Render.Value().c_str(), errno)); + TRACE(Trace::Error, (_T("Failed to open render node %s, error %d"), _renderNode.c_str(), errno)); return Core::ERROR_OPENING_FAILED; } @@ -980,10 +1273,18 @@ namespace Plugin { _adminLock.Unlock(); } - Core::ProxyType Texture(Core::ProxyType buffer) + Core::ProxyType CreateTexture(Core::ProxyType buffer) { ASSERT(buffer.IsValid()); - return _renderer->Texture(buffer); + return _renderer->CreateTexture(buffer); + } + + void DestroyTexture(Core::ProxyType texture) + { + if (texture.IsValid()) { + _renderer->DestroyTexture(texture->Identifier()); + texture.Release(); + } } void Announce(Exchange::IComposition::IClient* client) @@ -998,10 +1299,15 @@ namespace Plugin { void Revoke(Exchange::IComposition::IClient* client) { + ASSERT(client != nullptr); + + string clientName = client->Name(); _adminLock.Lock(); for (auto& observer : _observers) { - observer->Detached(client->Name()); + Core::IWorkerPool::Instance().Submit( + Core::ProxyType( + Core::ProxyType::Create(observer, clientName))); } _adminLock.Unlock(); @@ -1026,7 +1332,7 @@ namespace Plugin { } string Port() const override { - return string("Mesa3d"); + return _renderNode; } // Useless Resolution functions, this should be controlled by DisplayControl uint32_t Resolution(const Exchange::IComposition::ScreenResolution format VARIABLE_IS_NOT_USED) override @@ -1046,13 +1352,19 @@ namespace Plugin { private: void VSync(const Compositor::IOutput* output VARIABLE_IS_NOT_USED, const uint64_t sequence VARIABLE_IS_NOT_USED, const uint64_t pts VARIABLE_IS_NOT_USED /*usec since epoch*/) { + if (_terminated.load(std::memory_order_acquire)) { + return; + } + + // Signal Published to all clients - retired buffers can be released to GBM { std::lock_guard lock(_clientLock); _clients.Visit([&](const string& /*name*/, const Core::ProxyType client) { - client->Completed(); + client->SignalPublished(); }); } + // Allow next commit { std::lock_guard lock(_commitMutex); _canCommit.store(true, std::memory_order_release); @@ -1069,6 +1381,7 @@ namespace Plugin { void Render() { + _renderPending.store(true, std::memory_order_release); _present.Run(); } @@ -1094,55 +1407,75 @@ namespace Plugin { { const uint64_t start(Core::Time::Now().Ticks()); - ASSERT(_output != nullptr); - ASSERT(_output->IsValid() == true); - ASSERT(_renderer.IsValid() == true); + if (_output != nullptr) { + ASSERT(_output->IsValid() == true); + ASSERT(_renderer.IsValid() == true); - Core::ProxyType buffer = _output->Buffer(); + Core::ProxyType buffer = _output->Buffer(); + ASSERT(buffer.IsValid() == true); - ASSERT(buffer.IsValid() == true); + Core::ProxyType frameBuffer = buffer->FrameBuffer(); - Core::ProxyType frameBuffer = buffer->FrameBuffer(); + if (frameBuffer.IsValid() == true) { + _renderer->Bind(frameBuffer); - _renderer->Bind(frameBuffer); + _renderer->Begin(buffer->Width(), buffer->Height()); // set viewport for render - _renderer->Begin(buffer->Width(), buffer->Height()); // set viewport for render + _renderer->Clear(_background); - _renderer->Clear(_background); + { + std::lock_guard lock(_clientLock); - { - std::lock_guard lock(_clientLock); + _clients.Visit([&](const string& /*name*/, const Core::ProxyType client) { + RenderClient(client); // ~500-900 uS rpi4 + }); + } - _clients.Visit([&](const string& /*name*/, const Core::ProxyType client) { - RenderClient(client); // ~500-900 uS rpi4 - }); - } + _renderer->End(false); - _renderer->End(false); + // Ensure all rendering commands are finished on the GPU + uint32_t syncResult = _renderer->Finish(); + if (syncResult != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("GPU sync failed: %d"), syncResult)); + } - _renderer->Unbind(frameBuffer); + // NOW signal Rendered to all clients - GPU is done with all client buffers + { + std::lock_guard lock(_clientLock); + _clients.Visit([&](const string& /*name*/, const Core::ProxyType client) { + client->SignalRendered(); + }); + } - _totalRenderTime.fetch_add((Core::Time::Now().Ticks() - start), std::memory_order_relaxed); + _renderer->Unbind(frameBuffer); - // Block until VSync allows commit - { - std::unique_lock lock(_commitMutex); + _totalRenderTime.fetch_add((Core::Time::Now().Ticks() - start), std::memory_order_relaxed); - if (_commitCV.wait_for(lock, std::chrono::milliseconds(100), [this] { - return _canCommit.load(std::memory_order_acquire); // Wait for permission - })) { - // Got permission, reset it for next frame - _canCommit.store(false, std::memory_order_release); - } else { - TRACE(Trace::Error, (_T("Timeout waiting for VSync, forcing commit"))); - } - } + // Block until VSync allows commit + { + std::unique_lock lock(_commitMutex); + if (_commitCV.wait_for(lock, std::chrono::milliseconds(100), [this] { + return _canCommit.load(std::memory_order_acquire); // Wait for permission + })) { + // Got permission, reset it for next frame + _canCommit.store(false, std::memory_order_release); + } else { + TRACE(Trace::Error, (_T("Timeout waiting for VSync, forcing commit"))); + } + } - uint32_t commit = _output->Commit(); - ASSERT(commit == Core::ERROR_NONE); + if (_output != nullptr) { + uint32_t commit = _output->Commit(); + + if (commit != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("Commit failed: %d"), commit)); + } + } - _frameCount.fetch_add(1, std::memory_order_relaxed); - _frameTime.fetch_add((Core::Time::Now().Ticks() - start), std::memory_order_relaxed); + _frameCount.fetch_add(1, std::memory_order_relaxed); + _frameTime.fetch_add((Core::Time::Now().Ticks() - start), std::memory_order_relaxed); + } + } } void RenderClient(const Core::ProxyType client) @@ -1151,7 +1484,8 @@ namespace Plugin { const uint64_t start(Core::Time::Now().Ticks()); - Core::ProxyType texture = client->Acquire(); + bool isNewFrame = false; + Core::ProxyType texture = client->Acquire(isNewFrame); if ((texture.IsValid() == true)) { Exchange::IComposition::Rectangle renderBox; @@ -1172,16 +1506,21 @@ namespace Plugin { const float alpha = float(client->Opacity()) / float(Exchange::IComposition::maxOpacity); - _renderer->Render(texture, clientArea, clientProjection, alpha); - - client->Relinquish(); + _renderer->Render(texture->Identifier(), clientArea, clientProjection, alpha); const uint64_t duration = Core::Time::Now().Ticks() - start; UpdateClientStats(client->Name(), duration, false); } else { TRACE(Trace::Error, (_T("Skipping %s, no texture to render for buffer"), client->Name().c_str())); UpdateClientStats(client->Name(), 0, true); + + uint8_t currentId = client->CurrentBufferId(); + if (currentId < Client::MaxClientBuffers) { + client->IncrementBufferSkip(currentId); + } } + + client->Relinquish(isNewFrame); } class Presenter : public Core::Thread { @@ -1209,16 +1548,23 @@ namespace Plugin { private: uint32_t Worker() override { - Core::Thread::Block(); + while (IsRunning()) { + // Wait for signal + Core::Thread::Block(); - _parent.RenderOutput(); // 3000us + // Process one frame per wake-up + bool expected = true; + if (_parent._renderPending.compare_exchange_strong(expected, false, std::memory_order_acq_rel)) { + _parent.RenderOutput(); - // Print stats every 5 seconds - auto now = std::chrono::steady_clock::now(); + // Print stats every 5 seconds + auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - _lastStatsTime).count() >= 5) { - _parent.PrintStats(); - _lastStatsTime = now; + if (std::chrono::duration_cast(now - _lastStatsTime).count() >= 5) { + _parent.PrintStats(); + _lastStatsTime = now; + } + } } return Core::infinite; @@ -1270,20 +1616,13 @@ namespace Plugin { TRACE(Trace::Stats, (_T("Global: frames: %llu, avg: %llu µs , fps: %.2f"), frames, (totalRender / frames), fps)); } - std::lock_guard lock(_statsMutex); - for (auto& pair : _clientStats) { - const std::string& name = pair.first; - Client::Stats& stats = pair.second; - - uint64_t renders = stats.renderCount.exchange(0); - uint64_t totalTime = stats.totalRenderTime.exchange(0); - uint64_t skips = stats.skipCount.exchange(0); - - if (renders > 0 || skips > 0) { - uint64_t avgTime = renders > 0 ? totalTime / renders : 0; - TRACE(Trace::Stats, (_T("Client %s: %llu renders, %llu skips, avg: %llu µs"), name.c_str(), renders, skips, avgTime)); + std::lock_guard lock(_clientLock); + _clients.Visit([&](const string& name, const Core::ProxyType& client) { + if (client.IsValid()) { + string stats = client->Statistics().ToString(); + TRACE(Trace::Stats, (_T("=====%s======\n%s"), name.c_str(), stats.c_str())); } - } + }); } private: @@ -1312,6 +1651,8 @@ namespace Plugin { std::atomic _frameTime; mutable std::mutex _statsMutex; std::unordered_map _clientStats; + std::atomic _renderPending; + std::atomic _terminated; }; SERVICE_REGISTRATION(CompositorImplementation, 1, 0) diff --git a/Compositor/lib/Mesa/backend/src/DRM/Atomic.h b/Compositor/lib/Mesa/backend/src/DRM/Atomic.h index 3189afcac..cf900b275 100644 --- a/Compositor/lib/Mesa/backend/src/DRM/Atomic.h +++ b/Compositor/lib/Mesa/backend/src/DRM/Atomic.h @@ -30,7 +30,7 @@ namespace Compositor { namespace Backend { class Transaction { private: - static constexpr uint32_t DefaultDrmAtomicFlags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; + static constexpr uint32_t DefaultDrmAtomicFlags = DRM_MODE_PAGE_FLIP_EVENT; // Convert integer pixel value to DRM 16.16 fixed-point format static constexpr uint64_t ToFixedPoint16_16(uint32_t value) diff --git a/Compositor/lib/Mesa/backend/src/DRM/Connector.h b/Compositor/lib/Mesa/backend/src/DRM/Connector.h index 8ef46e6d4..f2a3075d4 100644 --- a/Compositor/lib/Mesa/backend/src/DRM/Connector.h +++ b/Compositor/lib/Mesa/backend/src/DRM/Connector.h @@ -42,6 +42,9 @@ namespace Compositor { : _swap() , _fd(-1) , _activePlane(~0) + , _buffer() + , _frameId() + , _frameBuffer() { // Double buffering: 0=current, 1=next, swap via XOR _buffer[0] = Core::ProxyType(); @@ -129,11 +132,7 @@ namespace Compositor { } Compositor::DRM::Identifier Id() const { - return _frameId[_activePlane]; // Current buffer's frame ID - } - Core::ProxyType Buffer() const - { - return _buffer[_activePlane]; + return _frameId[_activePlane]; } void Swap() { @@ -183,7 +182,7 @@ namespace Compositor { TRACE(Trace::Backend, ("Connector %d is not in a valid state", connectorId)); } else { // Apply scan results - _needsModeSet = scanResult.needsModeSet; + _needsModeSet.store(scanResult.needsModeSet, std::memory_order_relaxed); _selectedMode = scanResult.selectedMode; _dimensionsAdjusted = scanResult.dimensionsAdjusted; _gpuNode = scanResult.gpuNode; @@ -201,7 +200,7 @@ namespace Compositor { TRACE(Trace::Information, (_T("Selecting format... "))); - TRACE(Trace::Backend, ("DRM formats: %s", Compositor::ToString(drmFormats).c_str())); + TRACE(Trace::Backend, ("DRM formats: %s", Compositor::ToString(drmFormats).c_str())); TRACE(Trace::Backend, (_T("Renderer exposes %d render formats. "), renderer->RenderFormats().size())); TRACE(Trace::Backend, (_T("Backend accepts %d formats."), drmFormats.size())); @@ -234,7 +233,8 @@ namespace Compositor { bool NeedsModeSet() const { - return _needsModeSet; + // no hotplug detection by design. + return _needsModeSet.exchange(false, std::memory_order_acq_rel); } const drmModeModeInfo* SelectedMode() const @@ -335,7 +335,7 @@ namespace Compositor { } Core::ProxyType FrameBuffer() const override { - return (_frameBuffer.IsValid() == true) ? _frameBuffer.FrameBuffer() : Core::ProxyType(); + return (_frameBuffer.IsValid() == true) ? _frameBuffer.FrameBuffer() : Core::ProxyType(); } private: @@ -350,7 +350,7 @@ namespace Compositor { Compositor::IOutput::ICallback* _feedback; - bool _needsModeSet; + mutable std::atomic _needsModeSet; drmModeModeInfo _selectedMode; bool _dimensionsAdjusted; }; diff --git a/Compositor/lib/Mesa/backend/src/DRM/DRM.cpp b/Compositor/lib/Mesa/backend/src/DRM/DRM.cpp index 3795c1d2f..56d1cc33c 100644 --- a/Compositor/lib/Mesa/backend/src/DRM/DRM.cpp +++ b/Compositor/lib/Mesa/backend/src/DRM/DRM.cpp @@ -201,13 +201,11 @@ namespace Compositor { _connectors.Visit([&](const string& /*name*/, const Core::ProxyType connector) { connector->Presented(0, 0); // notify connector implementation the buffer failed to display. }); + } else { + if (doModeSet) { + _firstCommit = false; + } } - - if (doModeSet) { - _firstCommit = false; - } - - TRACE_GLOBAL(Trace::Information, ("Committed connectors: %u", result)); } else { TRACE_GLOBAL(Trace::Information, ("Commit in progress, skipping commit request")); result = Core::ERROR_INPROGRESS; @@ -228,8 +226,6 @@ namespace Compositor { presentationTimestamp.tv_nsec = useconds * 1000; connector->Presented(sequence, Core::Time(presentationTimestamp).Ticks()); - // TODO: Check with Bram the intention of the Release() - // connector->Release(); TRACE(Trace::Backend, ("Pageflip finished for %s", name.c_str())); } diff --git a/Compositor/lib/Mesa/include/IRenderer.h b/Compositor/lib/Mesa/include/IRenderer.h index 65cb875c2..7364af964 100644 --- a/Compositor/lib/Mesa/include/IRenderer.h +++ b/Compositor/lib/Mesa/include/IRenderer.h @@ -29,7 +29,7 @@ namespace Compositor { /** * @brief A frame buffer represents an image used as a target for rendering. */ - struct IFrameBuffer{ + struct IFrameBuffer { virtual ~IFrameBuffer() = default; virtual bool IsValid() const = 0; @@ -42,10 +42,13 @@ namespace Compositor { * @brief A texture represents an image used as a source for rendering. */ struct ITexture { + using Id = uint32_t; + virtual ~ITexture() = default; virtual bool IsValid() const = 0; + virtual Id Identifier() const = 0; virtual uint32_t Width() const = 0; virtual uint32_t Height() const = 0; }; // struct ITexture @@ -72,23 +75,35 @@ namespace Compositor { virtual uint32_t Unbind(const Core::ProxyType& framebuffer) = 0; /** - * @brief Start a render pass with the provided viewport. - * - * This should be called after a binding a buffer, callee must call - * End() when they are done rendering. + * @brief Begins recording rendering commands for the current frame. * * @param width Viewport width in pixels * @param height Viewport height in pixels - * @return false on failure, in which case compositors shouldn't try rendering. + * @return false on failure */ virtual bool Begin(uint32_t width, uint32_t height) = 0; /** - * @brief Ends a render pass. + * @brief Ends recording rendering commands for the current frame. + * + * After calling End(), the command buffer is closed but NOT yet submitted to GPU. + * Call Finish() to submit and wait for GPU completion. * + * @param dump If true, captures a snapshot of the rendered frame */ virtual void End(bool dump = false) = 0; + /** + * @brief Submits recorded commands to GPU and waits for completion. + * + * This call blocks until all rendering commands have finished executing on the GPU. + * Must be called after End() and before committing the frame buffer to display hardware. + * + * @param timeoutMs Maximum time to wait in milliseconds (default: 100ms) + * @return uint32_t Core::ERROR_NONE if rendering completed, Core::ERROR_TIMEDOUT on timeout + */ + virtual uint32_t Finish(uint32_t timeoutMs = 100) = 0; + /** * @brief Clear the viewport with the provided color * @@ -130,7 +145,10 @@ namespace Compositor { * @return A proxy to the ITexture representing the texture derived from * the provided composition buffer. */ - virtual Core::ProxyType Texture(const Core::ProxyType& buffer) = 0; + virtual Core::ProxyType CreateTexture(const Core::ProxyType& buffer) = 0; + + + virtual void DestroyTexture(ITexture::Id id) = 0; /** * @brief Renders a texture on the bound buffer at the given region with @@ -144,7 +162,7 @@ namespace Compositor { * * @return uint32_t Core::ERROR_NONE if all went ok, error code otherwise. */ - virtual uint32_t Render(const Core::ProxyType& texture, const Exchange::IComposition::Rectangle& region, const Matrix transform, float alpha) = 0; + virtual uint32_t Render(const ITexture::Id textureId, const Exchange::IComposition::Rectangle& region, const Matrix transform, float alpha) = 0; /** * @brief Renders a solid quadrangle* in the specified color with the specified matrix. diff --git a/Compositor/lib/Mesa/renderer/src/GL/EGL.cpp b/Compositor/lib/Mesa/renderer/src/GL/EGL.cpp index 829fe4a82..d0c0270d4 100644 --- a/Compositor/lib/Mesa/renderer/src/GL/EGL.cpp +++ b/Compositor/lib/Mesa/renderer/src/GL/EGL.cpp @@ -295,57 +295,71 @@ namespace Compositor { EGLDeviceEXT* eglDevices = static_cast(ALLOCA(nEglDevices * sizeof(EGLDeviceEXT))); - memset(eglDevices, 0, sizeof(nEglDevices * sizeof(EGLDeviceEXT))); + // FIXED: memset was wrong + memset(eglDevices, 0, nEglDevices * sizeof(EGLDeviceEXT)); _api.eglQueryDevicesEXT(nEglDevices, eglDevices, &nEglDevices); if (nEglDevices > 0) { - drmDevice* drmDevice = nullptr; - int ret = drmGetDevice(drmFd, &drmDevice); + drmDevice* drmDev = nullptr; + int ret = drmGetDevice(drmFd, &drmDev); - if ((ret == 0) && (drmDevice != nullptr)) { + if ((ret == 0) && (drmDev != nullptr)) { for (int i = 0; i < nEglDevices; i++) { - TRACE(Trace::EGL, ("Trying device %d [%p]", i, eglDevices[i])); + + EGLDeviceEXT dev = eglDevices[i]; + TRACE(Trace::EGL, ("Trying device %d [%p]", i, dev)); #ifdef __DEBUG__ - const char* extensions = _api.eglQueryDeviceStringEXT(eglDevices[i], EGL_EXTENSIONS); + const char* extensions = _api.eglQueryDeviceStringEXT(dev, EGL_EXTENSIONS); if (extensions != nullptr) { TRACE(Trace::EGL, ("EGL extensions: %s", extensions)); } #endif - const char* renderName = _api.eglQueryDeviceStringEXT(eglDevices[i], EGL_DRM_RENDER_NODE_FILE_EXT); - if (renderName != nullptr) { + const char* renderName = _api.eglQueryDeviceStringEXT(dev, EGL_DRM_RENDER_NODE_FILE_EXT); + + const char* deviceName = _api.eglQueryDeviceStringEXT(dev, EGL_DRM_DEVICE_FILE_EXT); + + if (renderName) TRACE(Trace::EGL, ("EGL render node: %s", renderName)); - } else { - TRACE(Trace::Error, ("No EGL render node associated to %p", eglDevices[i])); + else + TRACE(Trace::Error, ("No EGL render node associated to %p", dev)); + + if (deviceName) + TRACE(Trace::EGL, ("Found EGL device %s", deviceName)); + else { + TRACE(Trace::Error, ("No EGL device name returned for %p", dev)); + continue; } - const char* deviceName = _api.eglQueryDeviceStringEXT(eglDevices[i], EGL_DRM_DEVICE_FILE_EXT); - bool nodePresent(false); + bool renderMatch = false; + bool primaryMatch = false; - if (deviceName == nullptr) { - TRACE(Trace::Error, ("No EGL device name returned for %p", eglDevices[i])); - continue; - } else { - TRACE(Trace::EGL, ("Found EGL device %s", deviceName)); + // Prefer matching render node + if (renderName && (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) && strcmp(drmDev->nodes[DRM_NODE_RENDER], renderName) == 0) { + renderMatch = true; + } - for (uint16_t i = 0; i < DRM_NODE_MAX; i++) { - if ((drmDevice->available_nodes & (1 << i)) && (strcmp(drmDevice->nodes[i], deviceName) == 0)) { - nodePresent = true; - break; - } - } + // Then fallback to primary node + if (deviceName && (drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)) && strcmp(drmDev->nodes[DRM_NODE_PRIMARY], deviceName) == 0) { + primaryMatch = true; + } + + if (renderMatch) { + TRACE(Trace::EGL, ("Using EGL device (render node): %s", renderName)); + result = dev; + break; } - if ((deviceName != nullptr) && (nodePresent == true)) { - TRACE(Trace::EGL, ("Using EGL device %s", deviceName)); - result = eglDevices[i]; + if (primaryMatch) { + TRACE(Trace::EGL, ("Using EGL device (primary node fallback): %s", deviceName)); + result = dev; break; } } - drmFreeDevice(&drmDevice); + drmFreeDevice(&drmDev); } else { TRACE(Trace::Error, ("No DRM devices found")); } diff --git a/Compositor/lib/Mesa/renderer/src/GL/EGL.h b/Compositor/lib/Mesa/renderer/src/GL/EGL.h index 2b5422fea..290d010fa 100644 --- a/Compositor/lib/Mesa/renderer/src/GL/EGL.h +++ b/Compositor/lib/Mesa/renderer/src/GL/EGL.h @@ -122,6 +122,51 @@ namespace Compositor { EGLSurface _readSurface; }; + void DestroySync(EGLSync& sync) const + { + if (sync != EGL_NO_SYNC && _api.eglDestroySync != nullptr) { + _api.eglDestroySync(_display, sync); + sync = EGL_NO_SYNC; + } + } + + EGLSync CreateSync() const + { + EGLSync sync = EGL_NO_SYNC; + + if (_api.eglCreateSync != nullptr) { + sync = _api.eglCreateSync(_display, EGL_SYNC_FENCE, nullptr); + } + + return sync; + } + + EGLint WaitSync(EGLSync& sync, const EGLTime timeoutNs = EGL_FOREVER) const + { + EGLint status = EGL_UNSIGNALED; + + if (sync != EGL_NO_SYNC && _api.eglWaitSync != nullptr) { + status = _api.eglClientWaitSync(_display, sync, EGL_SYNC_FLUSH_COMMANDS_BIT, timeoutNs); + } + + return status; + } + + int ExportSyncAsFd(EGLSync sync) const + { + int fd = -1; + + if (sync != EGL_NO_SYNC && _api.eglDupNativeFenceFDANDROID != nullptr) { + fd = _api.eglDupNativeFenceFDANDROID(_display, sync); + + if (fd < 0) { + TRACE(Trace::Error, ("Failed to export sync as fd: 0x%x", eglGetError())); + } + } + + return fd; + } + private: EGLDeviceEXT FindEGLDevice(const int drmFd); uint32_t Initialize(EGLenum platform, void* remote_display, bool isMaster); diff --git a/Compositor/lib/Mesa/renderer/src/GL/GLES2.cpp b/Compositor/lib/Mesa/renderer/src/GL/GLES2.cpp index e5cc96d33..374756429 100644 --- a/Compositor/lib/Mesa/renderer/src/GL/GLES2.cpp +++ b/Compositor/lib/Mesa/renderer/src/GL/GLES2.cpp @@ -344,17 +344,13 @@ namespace Compositor { { GLES_DEBUG_SCOPE("FrameBuffer::Unbind"); ASSERT(eglGetCurrentContext() != EGL_NO_CONTEXT); - - glFlush(); glBindFramebuffer(GL_FRAMEBUFFER, 0); } void Bind() const override { GLES_DEBUG_SCOPE("FrameBuffer::Bind"); - ASSERT(eglGetCurrentContext() != EGL_NO_CONTEXT); - glBindFramebuffer(GL_FRAMEBUFFER, _glFrameBuffer); } @@ -813,102 +809,100 @@ namespace Compositor { GLESTexture& operator=(GLESTexture&&) = delete; GLESTexture& operator=(const GLESTexture&) = delete; - GLESTexture(GLES& parent, const Core::ProxyType& buffer) + GLESTexture(GLES& parent, const Core::ProxyType& buffer, ITexture::Id id) : _parent(parent) + , _id(id) , _target(GL_TEXTURE_2D) , _textureId(0) , _image(EGL_NO_IMAGE) , _buffer(buffer) + , _invalidated(false) { ASSERT(_buffer != nullptr); - - _parent.Add(this); - - if (buffer->Type() == Exchange::IGraphicsBuffer::TYPE_DMA) { - ImportDMABuffer(); - } - - if (buffer->Type() == Exchange::IGraphicsBuffer::TYPE_RAW) { - ImportSHMBuffer(); - } - - ASSERT(_textureId != 0); - - // Snapshot(); } ~GLESTexture() override { - _parent.Remove(this); - - Renderer::EGL::ContextBackup backup; - _parent.Egl().SetCurrent(); - - glDeleteTextures(1, &_textureId); - - if (_image != EGL_NO_IMAGE) { - _parent.Egl().DestroyImage(_image); - } - } - - /** - * IRenderer::ITexture - */ - virtual bool IsValid() const - { - return (_textureId != 0); } bool Invalidate() { - bool result( - (_buffer->Type() != Exchange::IGraphicsBuffer::TYPE_DMA) - || ((_image != EGL_NO_IMAGE) && (_target == GL_TEXTURE_EXTERNAL_OES))); - - if ((_image != EGL_NO_IMAGE) && (_target != GL_TEXTURE_EXTERNAL_OES)) { - Renderer::EGL::ContextBackup backup; - - _parent.Egl().SetCurrent(); - - glBindTexture(_target, _textureId); - _parent.Gles().glEGLImageTargetTexture2DOES(_target, _image); - glBindTexture(_target, 0); - } - return result; + return _invalidated.exchange(true, std::memory_order_relaxed); } - + + /** + * IRenderer::ITexture + */ + virtual bool IsValid() const override { return (_textureId != 0); } + ITexture::Id Identifier() const override { return _id; } uint32_t Width() const override { return _buffer->Width(); } uint32_t Height() const override { return _buffer->Height(); } GLenum Target() const { return _target; } GLuint Id() const { return _textureId; } + bool Draw(const float& alpha, const Matrix& matrix, const PointCoordinates& coordinates) const { - GLES_DEBUG_SCOPE("GLESTexture::Draw"); bool result(false); - bool hasAlpha(DRM::HasAlpha(_buffer->Format())); - if ((_target == GL_TEXTURE_EXTERNAL_OES)) { - ExternalProgram* program = _parent.Programs().QueryType(); - ASSERT(program != nullptr); - result = program->Draw(_textureId, _target, hasAlpha, alpha, matrix, coordinates); - } + if ((_invalidated.load(std::memory_order_relaxed) == false) && (IsValid() == true)) { + ASSERT(eglGetCurrentContext() != EGL_NO_CONTEXT); + + GLES_DEBUG_SCOPE("GLESTexture::Draw"); + + bool hasAlpha(DRM::HasAlpha(_buffer->Format())); - if ((_target == GL_TEXTURE_2D)) { - if (hasAlpha == true) { - RGBAProgram* program = _parent.Programs().QueryType(); + if ((_target == GL_TEXTURE_EXTERNAL_OES)) { + ExternalProgram* program = _parent.Programs().QueryType(); ASSERT(program != nullptr); result = program->Draw(_textureId, _target, hasAlpha, alpha, matrix, coordinates); - } else { - RGBXProgram* program = _parent.Programs().QueryType(); - ASSERT(program != nullptr); - result = program->Draw(_textureId, _target, false, alpha, matrix, coordinates); + } + + if ((_target == GL_TEXTURE_2D)) { + if (hasAlpha == true) { + RGBAProgram* program = _parent.Programs().QueryType(); + ASSERT(program != nullptr); + result = program->Draw(_textureId, _target, hasAlpha, alpha, matrix, coordinates); + } else { + RGBXProgram* program = _parent.Programs().QueryType(); + ASSERT(program != nullptr); + result = program->Draw(_textureId, _target, false, alpha, matrix, coordinates); + } } } return result; } + bool Initialize() + { + ASSERT(eglGetCurrentContext() != EGL_NO_CONTEXT); + + if (_textureId == 0) { + if (_buffer->Type() == Exchange::IGraphicsBuffer::TYPE_DMA) { + ImportDMABuffer(); + } else if (_buffer->Type() == Exchange::IGraphicsBuffer::TYPE_RAW) { + ImportSHMBuffer(); + } + } + return _textureId != 0; + } + + void Deinitialize() + { + ASSERT(eglGetCurrentContext() != EGL_NO_CONTEXT); + + if (_textureId != 0) { + glDeleteTextures(1, &_textureId); + _textureId = 0; + } + + if (_image != EGL_NO_IMAGE) { + _parent.Egl().DestroyImage(_image); + _image = EGL_NO_IMAGE; + } + } + private: void Snapshot() const { @@ -928,11 +922,6 @@ namespace Compositor { { bool external(false); - Renderer::EGL::ContextBackup backup; - - _parent.Egl().SetCurrent(); - ASSERT(eglGetError() == EGL_SUCCESS); - GLES_DEBUG_SCOPE("ImportDMABuffer"); _image = _parent.Egl().CreateImage(&(*_buffer), external); @@ -985,9 +974,6 @@ namespace Compositor { GLPixelFormat glFormat = ConvertFormat(_buffer->Format()); - Renderer::EGL::ContextBackup backup; - _parent.Egl().SetCurrent(); - GLES_DEBUG_SCOPE("ImportPixelBuffer"); glGenTextures(1, &_textureId); @@ -1017,10 +1003,12 @@ namespace Compositor { private: GLES& _parent; + const ITexture::Id _id; GLenum _target; GLuint _textureId; EGLImageKHR _image; Core::ProxyType _buffer; + std::atomic _invalidated; }; // class GLESTexture public: @@ -1034,6 +1022,9 @@ namespace Compositor { , _viewportHeight(0) , _programs() , _rendering(false) + , _nextTextureId(1) + , _textures() + , _pendingSync(EGL_NO_SYNC) { _egl.SetCurrent(); @@ -1072,21 +1063,17 @@ namespace Compositor { virtual ~GLES() { _egl.SetCurrent(); -#ifdef __DEBUG__ + _egl.DestroySync(_pendingSync); +#ifdef __DEBUG__ const std::string glExtensions(reinterpret_cast(glGetString(GL_EXTENSIONS))); if (API::HasExtension(glExtensions, "GL_KHR_debug")) { glDisable(GL_DEBUG_OUTPUT_KHR); _gles.glDebugMessageCallbackKHR(nullptr, nullptr); } - #endif - // glFinish(); - // glFlush(); - // glGetError(); - _egl.ResetCurrent(); TRACE(Trace::GL, ("GLES2 renderer %p destructed", this)); @@ -1129,6 +1116,8 @@ namespace Compositor { GLES_DEBUG_SCOPE("GLES::Begin"); + ProcessPending(); + _rendering = true; if (_gles.glGetGraphicsResetStatusKHR != nullptr) { @@ -1170,11 +1159,49 @@ namespace Compositor { } } - // nothing to do + if (_pendingSync != EGL_NO_SYNC) { + _egl.DestroySync(_pendingSync); + } + + _pendingSync = _egl.CreateSync(); + + if (_pendingSync == EGL_NO_SYNC) { + TRACE(Trace::Error, ("Failed to create GPU fence: 0x%x", eglGetError())); + } _rendering = false; } + uint32_t Finish(uint32_t timeoutMs) override + { + glFlush(); + + uint32_t result = Core::ERROR_NONE; + + if (_pendingSync == EGL_NO_SYNC) { + TRACE(Trace::GL, ("No GPU fence pending")); + return Core::ERROR_NONE; + } + + // Convert ms to ns for EGL API + const uint64_t timeoutNs = static_cast(timeoutMs) * 1000000; + + EGLint syncResult = _egl.WaitSync(_pendingSync, timeoutNs); + _egl.DestroySync(_pendingSync); + + if (syncResult != EGL_CONDITION_SATISFIED) { + if (syncResult == EGL_TIMEOUT_EXPIRED) { + TRACE(Trace::Error, ("GPU fence timeout after %u ms", timeoutMs)); + result = Core::ERROR_TIMEDOUT; + } else { + TRACE(Trace::Error, ("GPU fence wait failed: 0x%x", eglGetError())); + result = Core::ERROR_GENERAL; + } + } + + return result; + } + PUSH_WARNING(DISABLE_WARNING_OVERLOADED_VIRTUALS) void Clear(const Color color) override { @@ -1206,23 +1233,42 @@ namespace Compositor { return (Core::ProxyType(Core::ProxyType::Create(*this, buffer))); }; - Core::ProxyType Texture(const Core::ProxyType& buffer) override + Core::ProxyType CreateTexture(const Core::ProxyType& buffer) override { - return (Core::ProxyType(Core::ProxyType::Create(*this, buffer))); - }; + ITexture::Id id = _nextTextureId++; - uint32_t Render(const Core::ProxyType& texture, const Exchange::IComposition::Rectangle& region, const Matrix transformation, const float alpha) override - { - GLES_DEBUG_SCOPE("GLES::Render"); - ASSERT((_rendering == true) && (_egl.IsCurrent() == true) && (texture != nullptr)); + Core::ProxyType texture = _textures.Instance(id, *this, buffer, id); - const auto index = std::find(_textures.begin(), _textures.end(), &(*texture)); + if (texture.IsValid()) { + _queueLock.Lock(); + _importQueue.push_back(texture); + _queueLock.Unlock(); + } + return Core::ProxyType(texture); + } + + void DestroyTexture(ITexture::Id id) override + { + auto texture = _textures.Find(id); + if (texture.IsValid()) { + bool wasInvalidated = texture->Invalidate(); + if (!wasInvalidated) { // Only queue once + _queueLock.Lock(); + _removeQueue.push_back(texture); + _queueLock.Unlock(); + } + texture.Release(); + } + } + + uint32_t Render(const ITexture::Id id, const Exchange::IComposition::Rectangle& region, const Matrix transformation, const float alpha) override + { uint32_t result(Core::ERROR_BAD_REQUEST); - if (index != _textures.end()) { - const GLESTexture* glesTexture = reinterpret_cast(*index); + Core::ProxyType glesTexture = _textures.Find(id); + if (glesTexture.IsValid()) { Matrix gl_matrix; Transformation::Multiply(gl_matrix, _projection, transformation); Transformation::Multiply(gl_matrix, Transformation::Transformations[Transformation::TRANSFORM_NORMAL], transformation); @@ -1315,21 +1361,23 @@ namespace Compositor { return glGetError() == GL_NO_ERROR; } - void Add(IRenderer::ITexture* texture) + void ProcessPending() { - auto index = std::find(_textures.begin(), _textures.end(), texture); - - ASSERT(index == _textures.end()); - - _textures.emplace_back(texture); - } - - void Remove(IRenderer::ITexture* texture) - { - auto index = std::find(_textures.begin(), _textures.end(), texture); + _queueLock.Lock(); + std::vector> importWork = std::move(_importQueue); + std::vector> removeWork = std::move(_removeQueue); + _queueLock.Unlock(); + + for (auto& texture : importWork) { + if (texture.IsValid()) { + texture->Initialize(); + } + } - if (index != _textures.end()) { - _textures.erase(index); + for (auto& texture : removeWork) { + if (texture.IsValid()) { + texture->Deinitialize(); + } } } @@ -1338,8 +1386,6 @@ namespace Compositor { return _programs; } - using TextureRegister = std::list; - private: static API::GL _gles; @@ -1350,7 +1396,12 @@ namespace Compositor { Matrix _projection; ProgramRegistry _programs; mutable bool _rendering; - TextureRegister _textures; + std::atomic _nextTextureId; + Core::ProxyMapType _textures; + EGLSync _pendingSync; + std::vector> _importQueue; + std::vector> _removeQueue; + Core::CriticalSection _queueLock; }; // class GLES API::GL GLES::_gles; diff --git a/Compositor/lib/Mesa/renderer/src/GL/RenderAPI.h b/Compositor/lib/Mesa/renderer/src/GL/RenderAPI.h index e8518bfca..356ddfd3e 100644 --- a/Compositor/lib/Mesa/renderer/src/GL/RenderAPI.h +++ b/Compositor/lib/Mesa/renderer/src/GL/RenderAPI.h @@ -42,6 +42,13 @@ using PFNEGLDESTROYSYNCPROC = PFNEGLDESTROYSYNCKHRPROC; using PFNEGLWAITSYNCPROC = PFNEGLWAITSYNCKHRPROC; using PFNEGLCLIENTWAITSYNCPROC = PFNEGLCLIENTWAITSYNCKHRPROC; +using EGL_NO_SYNC = EGL_NO_SYNC_KHR; +using EGL_SYNC_FENCE = EGL_SYNC_FENCE_KHR; +using EGL_CONDITION_SATISFIED = EGL_CONDITION_SATISFIED_KHR; +using EGL_SYNC_FLUSH_COMMANDS_BIT = EGL_SYNC_FLUSH_COMMANDS_BIT_KHR; +using EGL_ALREADY_SIGNALED = EGL_ALREADY_SIGNALED_KHR; +using EGL_TIMEOUT_EXPIRED = EGL_TIMEOUT_EXPIRED_KHR; + constexpr char eglCreateImageProc[] = "eglCreateImageKHR"; constexpr char eglDestroyImageProc[] = "eglDestroyImageKHR"; constexpr char eglCreateSyncProc[] = "eglCreateSyncKHR"; @@ -307,6 +314,7 @@ namespace API { , eglClientWaitSync(nullptr) , eglExportDmaBufImageQueryMesa(nullptr) , eglExportDmaBufImageMesa(nullptr) + , eglDupNativeFenceFDANDROID(nullptr) { eglGetPlatformDisplayEXT = reinterpret_cast(eglGetProcAddress("eglGetPlatformDisplayEXT")); eglQueryDmaBufFormatsEXT = reinterpret_cast(eglGetProcAddress("eglQueryDmaBufFormatsEXT")); @@ -327,6 +335,8 @@ namespace API { eglExportDmaBufImageQueryMesa = reinterpret_cast(eglGetProcAddress("eglExportDMABUFImageQueryMESA")); eglExportDmaBufImageMesa = reinterpret_cast(eglGetProcAddress("eglExportDMABUFImageMESA")); + + eglDupNativeFenceFDANDROID = reinterpret_cast(eglGetProcAddress("eglDupNativeFenceFDANDROID")); } public: @@ -350,6 +360,8 @@ namespace API { PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC eglExportDmaBufImageQueryMesa; PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDmaBufImageMesa; + + PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID; }; // class EGL } // namespace API diff --git a/Compositor/lib/Mesa/renderer/test/testdmabuf.cpp b/Compositor/lib/Mesa/renderer/test/testdmabuf.cpp index 6868e9c0c..2f962bdc2 100644 --- a/Compositor/lib/Mesa/renderer/test/testdmabuf.cpp +++ b/Compositor/lib/Mesa/renderer/test/testdmabuf.cpp @@ -72,7 +72,7 @@ class RenderTest : public BaseTest { ASSERT(renderFd >= 0); _textureBuffer = Core::ProxyType::Create(renderFd, Texture::TvTexture); - _texture = renderer->Texture(Core::ProxyType(_textureBuffer)); + _texture = renderer->CreateTexture(Core::ProxyType(_textureBuffer)); ASSERT(_texture != nullptr); ASSERT(_texture->IsValid()); @@ -196,10 +196,11 @@ class RenderTest : public BaseTest { Compositor::Transformation::ProjectBox(matrix, renderBox, Compositor::Transformation::TRANSFORM_FLIPPED_180, rotation, renderer->Projection()); const Exchange::IComposition::Rectangle textureBox = { 0, 0, _texture->Width(), _texture->Height() }; - renderer->Render(_texture, textureBox, matrix, alpha); + renderer->Render(_texture->Identifier(), textureBox, matrix, alpha); renderer->End(false); renderer->Unbind(frameBuffer); + renderer->Finish(); uint32_t commit; if ((commit = connector->Commit()) == Core::ERROR_NONE) { diff --git a/Compositor/lib/Mesa/renderer/test/testtexture.cpp b/Compositor/lib/Mesa/renderer/test/testtexture.cpp index 01ad0bb31..e5fa2453a 100644 --- a/Compositor/lib/Mesa/renderer/test/testtexture.cpp +++ b/Compositor/lib/Mesa/renderer/test/testtexture.cpp @@ -85,7 +85,7 @@ class RenderTest : public BaseTest { , _renderStart(std::chrono::high_resolution_clock::now()) { auto renderer = Renderer(); - _texture = renderer->Texture(Core::ProxyType(textureTv)); + _texture = renderer->CreateTexture(Core::ProxyType(textureTv)); } virtual ~RenderTest() = default; @@ -127,10 +127,11 @@ class RenderTest : public BaseTest { Compositor::Transformation::ProjectBox(matrix, renderBox, Compositor::Transformation::TRANSFORM_FLIPPED_180, _rotation, renderer->Projection()); const Exchange::IComposition::Rectangle textureBox = { 0, 0, _texture->Width(), _texture->Height() }; - renderer->Render(_texture, textureBox, matrix, alpha); + renderer->Render(_texture->Identifier(), textureBox, matrix, alpha); renderer->End(false); renderer->Unbind(frameBuffer); + renderer->Finish(); connector->Commit();