mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Impeller] Passes and command buffers hold weak handles to the context. (flutter/engine#35352)
This makes the submission simpler as the allocators can be acquired from the pass itself. Also makes command buffer submission go through a common method that performs error checking before dispatch to the backend.
This commit is contained in:
parent
fea6e19d60
commit
a48523baec
@ -263,7 +263,7 @@ std::shared_ptr<Texture> ContentContext::MakeSubpass(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!sub_renderpass->EncodeCommands(context->GetResourceAllocator())) {
|
||||
if (!sub_renderpass->EncodeCommands()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@ -173,8 +173,7 @@ bool EntityPass::Render(ContentContext& renderer,
|
||||
entity.Render(renderer, *render_pass);
|
||||
}
|
||||
|
||||
if (!render_pass->EncodeCommands(
|
||||
renderer.GetContext()->GetResourceAllocator())) {
|
||||
if (!render_pass->EncodeCommands()) {
|
||||
return false;
|
||||
}
|
||||
if (!command_buffer->SubmitCommands()) {
|
||||
|
||||
@ -37,7 +37,7 @@ bool InlinePassContext::EndPass() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!pass_->EncodeCommands(context_->GetResourceAllocator())) {
|
||||
if (!pass_->EncodeCommands()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -244,7 +244,7 @@ bool Playground::OpenPlaygroundHere(Renderer::RenderCallback render_callback) {
|
||||
|
||||
ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass);
|
||||
|
||||
pass->EncodeCommands(renderer->GetContext()->GetResourceAllocator());
|
||||
pass->EncodeCommands();
|
||||
if (!buffer->SubmitCommands()) {
|
||||
return false;
|
||||
}
|
||||
@ -284,7 +284,7 @@ bool Playground::OpenPlaygroundHere(SinglePassCallback pass_callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pass->EncodeCommands(context->GetResourceAllocator());
|
||||
pass->EncodeCommands();
|
||||
if (!buffer->SubmitCommands()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -10,8 +10,10 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
CommandBufferGLES::CommandBufferGLES(ReactorGLES::Ref reactor)
|
||||
: reactor_(std::move(reactor)),
|
||||
CommandBufferGLES::CommandBufferGLES(std::weak_ptr<const Context> context,
|
||||
ReactorGLES::Ref reactor)
|
||||
: CommandBuffer(std::move(context)),
|
||||
reactor_(std::move(reactor)),
|
||||
is_valid_(reactor_ && reactor_->IsValid()) {}
|
||||
|
||||
CommandBufferGLES::~CommandBufferGLES() = default;
|
||||
@ -27,13 +29,8 @@ bool CommandBufferGLES::IsValid() const {
|
||||
}
|
||||
|
||||
// |CommandBuffer|
|
||||
bool CommandBufferGLES::SubmitCommands(CompletionCallback callback) {
|
||||
if (!IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CommandBufferGLES::OnSubmitCommands(CompletionCallback callback) {
|
||||
const auto result = reactor_->React();
|
||||
|
||||
if (callback) {
|
||||
callback(result ? CommandBuffer::Status::kCompleted
|
||||
: CommandBuffer::Status::kError);
|
||||
@ -48,7 +45,7 @@ std::shared_ptr<RenderPass> CommandBufferGLES::OnCreateRenderPass(
|
||||
return nullptr;
|
||||
}
|
||||
auto pass = std::shared_ptr<RenderPassGLES>(
|
||||
new RenderPassGLES(std::move(target), reactor_));
|
||||
new RenderPassGLES(context_, std::move(target), reactor_));
|
||||
if (!pass->IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -22,7 +22,8 @@ class CommandBufferGLES final : public CommandBuffer {
|
||||
ReactorGLES::Ref reactor_;
|
||||
bool is_valid_ = false;
|
||||
|
||||
CommandBufferGLES(ReactorGLES::Ref reactor);
|
||||
CommandBufferGLES(std::weak_ptr<const Context> context,
|
||||
ReactorGLES::Ref reactor);
|
||||
|
||||
// |CommandBuffer|
|
||||
void SetLabel(const std::string& label) const override;
|
||||
@ -31,7 +32,7 @@ class CommandBufferGLES final : public CommandBuffer {
|
||||
bool IsValid() const override;
|
||||
|
||||
// |CommandBuffer|
|
||||
bool SubmitCommands(CompletionCallback callback) override;
|
||||
bool OnSubmitCommands(CompletionCallback callback) override;
|
||||
|
||||
// |CommandBuffer|
|
||||
std::shared_ptr<RenderPass> OnCreateRenderPass(
|
||||
|
||||
@ -103,7 +103,8 @@ std::shared_ptr<PipelineLibrary> ContextGLES::GetPipelineLibrary() const {
|
||||
}
|
||||
|
||||
std::shared_ptr<CommandBuffer> ContextGLES::CreateCommandBuffer() const {
|
||||
return std::shared_ptr<CommandBufferGLES>(new CommandBufferGLES(reactor_));
|
||||
return std::shared_ptr<CommandBufferGLES>(
|
||||
new CommandBufferGLES(weak_from_this(), reactor_));
|
||||
}
|
||||
|
||||
// |Context|
|
||||
|
||||
@ -16,8 +16,10 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
RenderPassGLES::RenderPassGLES(RenderTarget target, ReactorGLES::Ref reactor)
|
||||
: RenderPass(std::move(target)),
|
||||
RenderPassGLES::RenderPassGLES(std::weak_ptr<const Context> context,
|
||||
RenderTarget target,
|
||||
ReactorGLES::Ref reactor)
|
||||
: RenderPass(std::move(context), std::move(target)),
|
||||
reactor_(std::move(reactor)),
|
||||
is_valid_(reactor_ && reactor_->IsValid()) {}
|
||||
|
||||
@ -455,8 +457,7 @@ struct RenderPassData {
|
||||
}
|
||||
|
||||
// |RenderPass|
|
||||
bool RenderPassGLES::EncodeCommands(
|
||||
const std::shared_ptr<Allocator>& transients_allocator) const {
|
||||
bool RenderPassGLES::OnEncodeCommands(const Context& context) const {
|
||||
if (!IsValid()) {
|
||||
return false;
|
||||
}
|
||||
@ -507,10 +508,11 @@ bool RenderPassGLES::EncodeCommands(
|
||||
CanDiscardAttachmentWhenDone(stencil0->store_action);
|
||||
}
|
||||
|
||||
return reactor_->AddOperation([pass_data, transients_allocator,
|
||||
return reactor_->AddOperation([pass_data,
|
||||
allocator = context.GetResourceAllocator(),
|
||||
commands = commands_](const auto& reactor) {
|
||||
auto result = EncodeCommandsInReactor(*pass_data, transients_allocator,
|
||||
reactor, commands);
|
||||
auto result =
|
||||
EncodeCommandsInReactor(*pass_data, allocator, reactor, commands);
|
||||
FML_CHECK(result) << "Must be able to encode GL commands without error.";
|
||||
});
|
||||
}
|
||||
|
||||
@ -22,7 +22,9 @@ class RenderPassGLES final : public RenderPass {
|
||||
std::string label_;
|
||||
bool is_valid_ = false;
|
||||
|
||||
RenderPassGLES(RenderTarget target, ReactorGLES::Ref reactor);
|
||||
RenderPassGLES(std::weak_ptr<const Context> context,
|
||||
RenderTarget target,
|
||||
ReactorGLES::Ref reactor);
|
||||
|
||||
// |RenderPass|
|
||||
bool IsValid() const override;
|
||||
@ -31,8 +33,7 @@ class RenderPassGLES final : public RenderPass {
|
||||
void OnSetLabel(std::string label) override;
|
||||
|
||||
// |RenderPass|
|
||||
bool EncodeCommands(
|
||||
const std::shared_ptr<Allocator>& transients_allocator) const override;
|
||||
bool OnEncodeCommands(const Context& context) const override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(RenderPassGLES);
|
||||
};
|
||||
|
||||
@ -13,8 +13,6 @@ namespace impeller {
|
||||
|
||||
class CommandBufferMTL final : public CommandBuffer {
|
||||
public:
|
||||
CommandBufferMTL();
|
||||
|
||||
// |CommandBuffer|
|
||||
~CommandBufferMTL() override;
|
||||
|
||||
@ -23,7 +21,8 @@ class CommandBufferMTL final : public CommandBuffer {
|
||||
|
||||
id<MTLCommandBuffer> buffer_ = nullptr;
|
||||
|
||||
CommandBufferMTL(id<MTLCommandQueue> queue);
|
||||
CommandBufferMTL(const std::weak_ptr<const Context> context,
|
||||
id<MTLCommandQueue> queue);
|
||||
|
||||
// |CommandBuffer|
|
||||
void SetLabel(const std::string& label) const override;
|
||||
@ -32,7 +31,7 @@ class CommandBufferMTL final : public CommandBuffer {
|
||||
bool IsValid() const override;
|
||||
|
||||
// |CommandBuffer|
|
||||
bool SubmitCommands(CompletionCallback callback) override;
|
||||
bool OnSubmitCommands(CompletionCallback callback) override;
|
||||
|
||||
// |CommandBuffer|
|
||||
std::shared_ptr<RenderPass> OnCreateRenderPass(
|
||||
|
||||
@ -137,8 +137,9 @@ id<MTLCommandBuffer> CreateCommandBuffer(id<MTLCommandQueue> queue) {
|
||||
return [queue commandBuffer];
|
||||
}
|
||||
|
||||
CommandBufferMTL::CommandBufferMTL(id<MTLCommandQueue> queue)
|
||||
: buffer_(CreateCommandBuffer(queue)) {}
|
||||
CommandBufferMTL::CommandBufferMTL(const std::weak_ptr<const Context> context,
|
||||
id<MTLCommandQueue> queue)
|
||||
: CommandBuffer(std::move(context)), buffer_(CreateCommandBuffer(queue)) {}
|
||||
|
||||
CommandBufferMTL::~CommandBufferMTL() = default;
|
||||
|
||||
@ -166,15 +167,7 @@ static CommandBuffer::Status ToCommitResult(MTLCommandBufferStatus status) {
|
||||
return CommandBufferMTL::Status::kError;
|
||||
}
|
||||
|
||||
bool CommandBufferMTL::SubmitCommands(CompletionCallback callback) {
|
||||
if (!IsValid()) {
|
||||
// Already committed or was never valid. Either way, this is caller error.
|
||||
if (callback) {
|
||||
callback(Status::kError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CommandBufferMTL::OnSubmitCommands(CompletionCallback callback) {
|
||||
if (callback) {
|
||||
[buffer_ addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
|
||||
LogMTLCommandBufferErrorIfPresent(buffer);
|
||||
@ -195,7 +188,7 @@ std::shared_ptr<RenderPass> CommandBufferMTL::OnCreateRenderPass(
|
||||
}
|
||||
|
||||
auto pass = std::shared_ptr<RenderPassMTL>(
|
||||
new RenderPassMTL(buffer_, std::move(target)));
|
||||
new RenderPassMTL(context_, std::move(target), buffer_));
|
||||
if (!pass->IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -193,7 +193,8 @@ std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBufferInQueue(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto buffer = std::shared_ptr<CommandBufferMTL>(new CommandBufferMTL(queue));
|
||||
auto buffer = std::shared_ptr<CommandBufferMTL>(
|
||||
new CommandBufferMTL(weak_from_this(), queue));
|
||||
if (!buffer->IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -25,7 +25,9 @@ class RenderPassMTL final : public RenderPass {
|
||||
std::string label_;
|
||||
bool is_valid_ = false;
|
||||
|
||||
RenderPassMTL(id<MTLCommandBuffer> buffer, RenderTarget target);
|
||||
RenderPassMTL(std::weak_ptr<const Context> context,
|
||||
RenderTarget target,
|
||||
id<MTLCommandBuffer> buffer);
|
||||
|
||||
// |RenderPass|
|
||||
bool IsValid() const override;
|
||||
@ -34,8 +36,7 @@ class RenderPassMTL final : public RenderPass {
|
||||
void OnSetLabel(std::string label) override;
|
||||
|
||||
// |RenderPass|
|
||||
bool EncodeCommands(
|
||||
const std::shared_ptr<Allocator>& transients_allocator) const override;
|
||||
bool OnEncodeCommands(const Context& context) const override;
|
||||
|
||||
bool EncodeCommands(const std::shared_ptr<Allocator>& transients_allocator,
|
||||
id<MTLRenderCommandEncoder> pass) const;
|
||||
|
||||
@ -128,8 +128,10 @@ static MTLRenderPassDescriptor* ToMTLRenderPassDescriptor(
|
||||
return result;
|
||||
}
|
||||
|
||||
RenderPassMTL::RenderPassMTL(id<MTLCommandBuffer> buffer, RenderTarget target)
|
||||
: RenderPass(std::move(target)),
|
||||
RenderPassMTL::RenderPassMTL(std::weak_ptr<const Context> context,
|
||||
RenderTarget target,
|
||||
id<MTLCommandBuffer> buffer)
|
||||
: RenderPass(std::move(context), std::move(target)),
|
||||
buffer_(buffer),
|
||||
desc_(ToMTLRenderPassDescriptor(GetRenderTarget())) {
|
||||
if (!buffer_ || !desc_ || !render_target_.IsValid()) {
|
||||
@ -151,8 +153,7 @@ void RenderPassMTL::OnSetLabel(std::string label) {
|
||||
label_ = std::move(label);
|
||||
}
|
||||
|
||||
bool RenderPassMTL::EncodeCommands(
|
||||
const std::shared_ptr<Allocator>& transients_allocator) const {
|
||||
bool RenderPassMTL::OnEncodeCommands(const Context& context) const {
|
||||
TRACE_EVENT0("impeller", "RenderPassMTL::EncodeCommands");
|
||||
if (!IsValid()) {
|
||||
return false;
|
||||
@ -173,7 +174,7 @@ bool RenderPassMTL::EncodeCommands(
|
||||
fml::ScopedCleanupClosure auto_end(
|
||||
[render_command_encoder]() { [render_command_encoder endEncoding]; });
|
||||
|
||||
return EncodeCommands(transients_allocator, render_command_encoder);
|
||||
return EncodeCommands(context.GetResourceAllocator(), render_command_encoder);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
CommandBufferVK::CommandBufferVK() = default;
|
||||
CommandBufferVK::CommandBufferVK(std::weak_ptr<const Context> context)
|
||||
: CommandBuffer(std::move(context)) {}
|
||||
|
||||
CommandBufferVK::~CommandBufferVK() = default;
|
||||
|
||||
@ -21,7 +22,7 @@ bool CommandBufferVK::IsValid() const {
|
||||
FML_UNREACHABLE();
|
||||
}
|
||||
|
||||
bool CommandBufferVK::SubmitCommands(CompletionCallback callback) {
|
||||
bool CommandBufferVK::OnSubmitCommands(CompletionCallback callback) {
|
||||
FML_UNREACHABLE();
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ class CommandBufferVK final : public CommandBuffer {
|
||||
private:
|
||||
friend class ContextMTL;
|
||||
|
||||
CommandBufferVK();
|
||||
explicit CommandBufferVK(std::weak_ptr<const Context> context);
|
||||
|
||||
// |CommandBuffer|
|
||||
void SetLabel(const std::string& label) const override;
|
||||
@ -26,7 +26,7 @@ class CommandBufferVK final : public CommandBuffer {
|
||||
bool IsValid() const override;
|
||||
|
||||
// |CommandBuffer|
|
||||
bool SubmitCommands(CompletionCallback callback) override;
|
||||
bool OnSubmitCommands(CompletionCallback callback) override;
|
||||
|
||||
// |CommandBuffer|
|
||||
std::shared_ptr<RenderPass> OnCreateRenderPass(
|
||||
|
||||
@ -27,8 +27,7 @@ class RenderPassVK final : public RenderPass {
|
||||
void OnSetLabel(std::string label) override;
|
||||
|
||||
// |RenderPass|
|
||||
bool EncodeCommands(
|
||||
const std::shared_ptr<Allocator>& transients_allocator) const override;
|
||||
bool OnEncodeCommands(const Context& context) const override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(RenderPassVK);
|
||||
};
|
||||
|
||||
@ -4,15 +4,29 @@
|
||||
|
||||
#include "impeller/renderer/command_buffer.h"
|
||||
|
||||
#include "flutter/fml/trace_event.h"
|
||||
#include "impeller/renderer/render_pass.h"
|
||||
#include "impeller/renderer/render_target.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
CommandBuffer::CommandBuffer() = default;
|
||||
CommandBuffer::CommandBuffer(std::weak_ptr<const Context> context)
|
||||
: context_(std::move(context)) {}
|
||||
|
||||
CommandBuffer::~CommandBuffer() = default;
|
||||
|
||||
bool CommandBuffer::SubmitCommands(CompletionCallback callback) {
|
||||
TRACE_EVENT0("impeller", "CommandBuffer::SubmitCommands");
|
||||
if (!IsValid()) {
|
||||
// Already committed or was never valid. Either way, this is caller error.
|
||||
if (callback) {
|
||||
callback(Status::kError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return OnSubmitCommands(callback);
|
||||
}
|
||||
|
||||
bool CommandBuffer::SubmitCommands() {
|
||||
return SubmitCommands(nullptr);
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ class CommandBuffer {
|
||||
///
|
||||
/// @param[in] callback The completion callback.
|
||||
///
|
||||
[[nodiscard]] virtual bool SubmitCommands(CompletionCallback callback) = 0;
|
||||
[[nodiscard]] bool SubmitCommands(CompletionCallback callback);
|
||||
|
||||
[[nodiscard]] bool SubmitCommands();
|
||||
|
||||
@ -82,13 +82,17 @@ class CommandBuffer {
|
||||
std::shared_ptr<BlitPass> CreateBlitPass() const;
|
||||
|
||||
protected:
|
||||
CommandBuffer();
|
||||
std::weak_ptr<const Context> context_;
|
||||
|
||||
explicit CommandBuffer(std::weak_ptr<const Context> context);
|
||||
|
||||
virtual std::shared_ptr<RenderPass> OnCreateRenderPass(
|
||||
RenderTarget render_target) const = 0;
|
||||
|
||||
virtual std::shared_ptr<BlitPass> OnCreateBlitPass() const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool OnSubmitCommands(CompletionCallback callback) = 0;
|
||||
|
||||
private:
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(CommandBuffer);
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ class CommandBuffer;
|
||||
class PipelineLibrary;
|
||||
class Allocator;
|
||||
|
||||
class Context {
|
||||
class Context : public std::enable_shared_from_this<Context> {
|
||||
public:
|
||||
virtual ~Context();
|
||||
|
||||
|
||||
@ -6,8 +6,10 @@
|
||||
|
||||
namespace impeller {
|
||||
|
||||
RenderPass::RenderPass(RenderTarget target)
|
||||
: render_target_(std::move(target)),
|
||||
RenderPass::RenderPass(std::weak_ptr<const Context> context,
|
||||
RenderTarget target)
|
||||
: context_(std::move(context)),
|
||||
render_target_(std::move(target)),
|
||||
transients_buffer_(HostBuffer::Create()) {}
|
||||
|
||||
RenderPass::~RenderPass() = default;
|
||||
@ -63,4 +65,13 @@ bool RenderPass::AddCommand(Command command) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderPass::EncodeCommands() const {
|
||||
auto context = context_.lock();
|
||||
// The context could have been collected in the meantime.
|
||||
if (!context) {
|
||||
return false;
|
||||
}
|
||||
return OnEncodeCommands(*context);
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -51,23 +51,23 @@ class RenderPass {
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Encode the recorded commands to the underlying command buffer.
|
||||
///
|
||||
/// @param transients_allocator The transients allocator.
|
||||
///
|
||||
/// @return If the commands were encoded to the underlying command
|
||||
/// buffer.
|
||||
///
|
||||
virtual bool EncodeCommands(
|
||||
const std::shared_ptr<Allocator>& transients_allocator) const = 0;
|
||||
bool EncodeCommands() const;
|
||||
|
||||
protected:
|
||||
const std::weak_ptr<const Context> context_;
|
||||
const RenderTarget render_target_;
|
||||
std::shared_ptr<HostBuffer> transients_buffer_;
|
||||
std::vector<Command> commands_;
|
||||
|
||||
RenderPass(RenderTarget target);
|
||||
RenderPass(std::weak_ptr<const Context> context, RenderTarget target);
|
||||
|
||||
virtual void OnSetLabel(std::string label) = 0;
|
||||
|
||||
virtual bool OnEncodeCommands(const Context& context) const = 0;
|
||||
|
||||
private:
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(RenderPass);
|
||||
};
|
||||
|
||||
@ -53,8 +53,11 @@ bool Renderer::Render(std::unique_ptr<Surface> surface,
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto present_result = surface->Present();
|
||||
|
||||
frames_in_flight_sema_->Signal();
|
||||
return surface->Present();
|
||||
|
||||
return present_result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Context> Renderer::GetContext() const {
|
||||
|
||||
@ -378,7 +378,7 @@ TEST_P(RendererTest, CanRenderToTexture) {
|
||||
VS::BindUniformBuffer(
|
||||
cmd, r2t_pass->GetTransientsBuffer().EmplaceUniform(uniforms));
|
||||
ASSERT_TRUE(r2t_pass->AddCommand(std::move(cmd)));
|
||||
ASSERT_TRUE(r2t_pass->EncodeCommands(context->GetResourceAllocator()));
|
||||
ASSERT_TRUE(r2t_pass->EncodeCommands());
|
||||
}
|
||||
|
||||
#if IMPELLER_ENABLE_METAL
|
||||
@ -543,7 +543,7 @@ TEST_P(RendererTest, CanBlitTextureToTexture) {
|
||||
|
||||
pass->AddCommand(std::move(cmd));
|
||||
}
|
||||
pass->EncodeCommands(context->GetResourceAllocator());
|
||||
pass->EncodeCommands();
|
||||
}
|
||||
|
||||
if (!buffer->SubmitCommands()) {
|
||||
@ -664,7 +664,7 @@ TEST_P(RendererTest, CanGenerateMipmaps) {
|
||||
|
||||
pass->AddCommand(std::move(cmd));
|
||||
}
|
||||
pass->EncodeCommands(context->GetResourceAllocator());
|
||||
pass->EncodeCommands();
|
||||
}
|
||||
|
||||
if (!buffer->SubmitCommands()) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user