Merge "[HWUI] Remove references to gui/Surface."
This commit is contained in:
committed by
Android (Google) Code Review
commit
805edec77d
@@ -17,40 +17,36 @@
|
||||
#define LOG_TAG "ThreadedRenderer"
|
||||
#define ATRACE_TAG ATRACE_TAG_VIEW
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "jni.h"
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include "core_jni_helpers.h"
|
||||
#include <GraphicsJNI.h>
|
||||
|
||||
#include <gui/BufferItemConsumer.h>
|
||||
#include <gui/BufferQueue.h>
|
||||
#include <gui/Surface.h>
|
||||
|
||||
#include "android_graphics_HardwareRendererObserver.h"
|
||||
|
||||
#include <private/EGL/cache.h>
|
||||
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/TraceUtils.h>
|
||||
#include <android_runtime/android_view_Surface.h>
|
||||
#include <system/window.h>
|
||||
|
||||
#include <FrameInfo.h>
|
||||
#include <GraphicsJNI.h>
|
||||
#include <Picture.h>
|
||||
#include <Properties.h>
|
||||
#include <RootRenderNode.h>
|
||||
#include <dlfcn.h>
|
||||
#include <gui/BufferItemConsumer.h>
|
||||
#include <gui/BufferQueue.h>
|
||||
#include <gui/Surface.h>
|
||||
#include <inttypes.h>
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include <pipeline/skia/ShaderCache.h>
|
||||
#include <private/EGL/cache.h>
|
||||
#include <renderthread/CanvasContext.h>
|
||||
#include <renderthread/RenderProxy.h>
|
||||
#include <renderthread/RenderTask.h>
|
||||
#include <renderthread/RenderThread.h>
|
||||
#include <pipeline/skia/ShaderCache.h>
|
||||
#include <system/window.h>
|
||||
#include <utils/Color.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/TraceUtils.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
#include "android_graphics_HardwareRendererObserver.h"
|
||||
#include "core_jni_helpers.h"
|
||||
#include "jni.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
@@ -78,6 +74,9 @@ static JNIEnv* getenv(JavaVM* vm) {
|
||||
return env;
|
||||
}
|
||||
|
||||
typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
|
||||
ANW_fromSurface fromSurface;
|
||||
|
||||
class JvmErrorReporter : public ErrorHandler {
|
||||
public:
|
||||
JvmErrorReporter(JNIEnv* env) {
|
||||
@@ -178,9 +177,9 @@ static void android_view_ThreadedRenderer_setName(JNIEnv* env, jobject clazz,
|
||||
static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz,
|
||||
jlong proxyPtr, jobject jsurface, jboolean discardBuffer) {
|
||||
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
|
||||
sp<Surface> surface;
|
||||
ANativeWindow* window = nullptr;
|
||||
if (jsurface) {
|
||||
surface = android_view_Surface_getSurface(env, jsurface);
|
||||
window = fromSurface(env, jsurface);
|
||||
}
|
||||
bool enableTimeout = true;
|
||||
if (discardBuffer) {
|
||||
@@ -188,7 +187,7 @@ static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz,
|
||||
enableTimeout = false;
|
||||
proxy->setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
|
||||
}
|
||||
proxy->setSurface(surface, enableTimeout);
|
||||
proxy->setSurface(window, enableTimeout);
|
||||
}
|
||||
|
||||
static jboolean android_view_ThreadedRenderer_pause(JNIEnv* env, jobject clazz,
|
||||
@@ -458,8 +457,10 @@ static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env,
|
||||
jint right, jint bottom, jlong bitmapPtr) {
|
||||
SkBitmap bitmap;
|
||||
bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
|
||||
sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
|
||||
return RenderProxy::copySurfaceInto(surface, left, top, right, bottom, &bitmap);
|
||||
ANativeWindow* window = fromSurface(env, jsurface);
|
||||
jint result = RenderProxy::copySurfaceInto(window, left, top, right, bottom, &bitmap);
|
||||
ANativeWindow_release(window);
|
||||
return result;
|
||||
}
|
||||
|
||||
class ContextFactory : public IContextFactory {
|
||||
@@ -480,6 +481,7 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(
|
||||
uint32_t width = jwidth;
|
||||
uint32_t height = jheight;
|
||||
|
||||
// TODO: should this be generated from an AImageReader?
|
||||
// Create a Surface wired up to a BufferItemConsumer
|
||||
sp<IGraphicBufferProducer> producer;
|
||||
sp<IGraphicBufferConsumer> rawConsumer;
|
||||
@@ -496,7 +498,7 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(
|
||||
ContextFactory factory;
|
||||
RenderProxy proxy{true, renderNode, &factory};
|
||||
proxy.setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
|
||||
proxy.setSurface(surface);
|
||||
proxy.setSurface(surface.get());
|
||||
// Shadows can't be used via this interface, so just set the light source
|
||||
// to all 0s.
|
||||
proxy.setLightAlpha(0, 0);
|
||||
@@ -722,6 +724,11 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
|
||||
gFrameCompleteCallback.onFrameComplete = GetMethodIDOrDie(env, frameCompleteClass,
|
||||
"onFrameComplete", "(J)V");
|
||||
|
||||
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
|
||||
fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface");
|
||||
LOG_ALWAYS_FATAL_IF(fromSurface == nullptr,
|
||||
"Failed to find required symbol ANativeWindow_fromSurface!");
|
||||
|
||||
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SkiaPipeline.h"
|
||||
#include <EGL/egl.h>
|
||||
#include <system/window.h>
|
||||
|
||||
#include "SkiaPipeline.h"
|
||||
#include "renderstate/RenderState.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
@@ -24,18 +24,19 @@
|
||||
#include <SkOverdrawColorFilter.h>
|
||||
#include <SkPicture.h>
|
||||
#include <SkPictureRecorder.h>
|
||||
#include <SkTypeface.h>
|
||||
#include <SkSerialProcs.h>
|
||||
#include <SkTypeface.h>
|
||||
#include <android-base/properties.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "LightingInfo.h"
|
||||
#include "VectorDrawable.h"
|
||||
#include "thread/CommonPool.h"
|
||||
#include "tools/SkSharingProc.h"
|
||||
#include "utils/TraceUtils.h"
|
||||
#include "utils/String8.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android-base/properties.h>
|
||||
#include "utils/TraceUtils.h"
|
||||
|
||||
using namespace android::uirenderer::renderthread;
|
||||
|
||||
|
||||
@@ -139,15 +139,15 @@ void CanvasContext::destroy() {
|
||||
mAnimationContext->destroy();
|
||||
}
|
||||
|
||||
void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) {
|
||||
void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
|
||||
ATRACE_CALL();
|
||||
|
||||
if (surface) {
|
||||
mNativeSurface = std::make_unique<ReliableSurface>(std::move(surface));
|
||||
if (window) {
|
||||
mNativeSurface = std::make_unique<ReliableSurface>(window);
|
||||
mNativeSurface->init();
|
||||
if (enableTimeout) {
|
||||
// TODO: Fix error handling & re-shorten timeout
|
||||
ANativeWindow_setDequeueTimeout(mNativeSurface->getNativeWindow(), 4000_ms);
|
||||
ANativeWindow_setDequeueTimeout(window, 4000_ms);
|
||||
}
|
||||
} else {
|
||||
mNativeSurface = nullptr;
|
||||
@@ -167,7 +167,7 @@ void CanvasContext::setSurface(sp<Surface>&& surface, bool enableTimeout) {
|
||||
|
||||
mFrameNumber = -1;
|
||||
|
||||
if (hasSurface) {
|
||||
if (window != nullptr && hasSurface) {
|
||||
mHaveNewSurface = true;
|
||||
mSwapHistory.clear();
|
||||
// Enable frame stats after the surface has been bound to the appropriate graphics API.
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include <SkRect.h>
|
||||
#include <SkSize.h>
|
||||
#include <cutils/compiler.h>
|
||||
#include <gui/Surface.h>
|
||||
#include <utils/Functor.h>
|
||||
|
||||
#include <functional>
|
||||
@@ -111,7 +110,7 @@ public:
|
||||
// Won't take effect until next EGLSurface creation
|
||||
void setSwapBehavior(SwapBehavior swapBehavior);
|
||||
|
||||
void setSurface(sp<Surface>&& surface, bool enableTimeout = true);
|
||||
void setSurface(ANativeWindow* window, bool enableTimeout = true);
|
||||
bool pauseSurface();
|
||||
void setStopped(bool stopped);
|
||||
bool hasSurface() const { return mNativeSurface.get(); }
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
#include "ReliableSurface.h"
|
||||
|
||||
#include <log/log_main.h>
|
||||
#include <private/android/AHardwareBufferHelpers.h>
|
||||
// TODO: this should be including apex instead.
|
||||
#include <vndk/window.h>
|
||||
|
||||
namespace android::uirenderer::renderthread {
|
||||
|
||||
@@ -26,8 +29,9 @@ namespace android::uirenderer::renderthread {
|
||||
// to propagate this error back to the caller
|
||||
constexpr bool DISABLE_BUFFER_PREFETCH = true;
|
||||
|
||||
ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) {
|
||||
LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr");
|
||||
ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) {
|
||||
LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr");
|
||||
ANativeWindow_acquire(mWindow);
|
||||
}
|
||||
|
||||
ReliableSurface::~ReliableSurface() {
|
||||
@@ -36,26 +40,27 @@ ReliableSurface::~ReliableSurface() {
|
||||
// As a concrete example, if the underlying ANativeWindow is associated with
|
||||
// an EGLSurface that is still in use, then if we don't clear out the
|
||||
// interceptors then we walk into undefined behavior.
|
||||
ANativeWindow_setCancelBufferInterceptor(mSurface.get(), nullptr, nullptr);
|
||||
ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), nullptr, nullptr);
|
||||
ANativeWindow_setQueueBufferInterceptor(mSurface.get(), nullptr, nullptr);
|
||||
ANativeWindow_setPerformInterceptor(mSurface.get(), nullptr, nullptr);
|
||||
ANativeWindow_setCancelBufferInterceptor(mWindow, nullptr, nullptr);
|
||||
ANativeWindow_setDequeueBufferInterceptor(mWindow, nullptr, nullptr);
|
||||
ANativeWindow_setQueueBufferInterceptor(mWindow, nullptr, nullptr);
|
||||
ANativeWindow_setPerformInterceptor(mWindow, nullptr, nullptr);
|
||||
ANativeWindow_release(mWindow);
|
||||
}
|
||||
|
||||
void ReliableSurface::init() {
|
||||
int result = ANativeWindow_setCancelBufferInterceptor(mSurface.get(), hook_cancelBuffer, this);
|
||||
int result = ANativeWindow_setCancelBufferInterceptor(mWindow, hook_cancelBuffer, this);
|
||||
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set cancelBuffer interceptor: error = %d",
|
||||
result);
|
||||
|
||||
result = ANativeWindow_setDequeueBufferInterceptor(mSurface.get(), hook_dequeueBuffer, this);
|
||||
result = ANativeWindow_setDequeueBufferInterceptor(mWindow, hook_dequeueBuffer, this);
|
||||
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set dequeueBuffer interceptor: error = %d",
|
||||
result);
|
||||
|
||||
result = ANativeWindow_setQueueBufferInterceptor(mSurface.get(), hook_queueBuffer, this);
|
||||
result = ANativeWindow_setQueueBufferInterceptor(mWindow, hook_queueBuffer, this);
|
||||
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set queueBuffer interceptor: error = %d",
|
||||
result);
|
||||
|
||||
result = ANativeWindow_setPerformInterceptor(mSurface.get(), hook_perform, this);
|
||||
result = ANativeWindow_setPerformInterceptor(mWindow, hook_perform, this);
|
||||
LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set perform interceptor: error = %d",
|
||||
result);
|
||||
}
|
||||
@@ -87,7 +92,7 @@ int ReliableSurface::reserveNext() {
|
||||
ANativeWindowBuffer* buffer = nullptr;
|
||||
|
||||
// Note that this calls back into our own hooked method.
|
||||
int result = ANativeWindow_dequeueBuffer(mSurface.get(), &buffer, &fenceFd);
|
||||
int result = ANativeWindow_dequeueBuffer(mWindow, &buffer, &fenceFd);
|
||||
|
||||
{
|
||||
std::lock_guard _lock{mMutex};
|
||||
@@ -117,7 +122,7 @@ void ReliableSurface::clearReservedBuffer() {
|
||||
// Note that clearReservedBuffer may be reentrant here, so
|
||||
// mReservedBuffer must be cleared once we reach here to avoid recursing
|
||||
// forever.
|
||||
ANativeWindow_cancelBuffer(mSurface.get(), buffer, releaseFd);
|
||||
ANativeWindow_cancelBuffer(mWindow, buffer, releaseFd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,10 +244,10 @@ int ReliableSurface::hook_perform(ANativeWindow* window, ANativeWindow_performFn
|
||||
case ANATIVEWINDOW_PERFORM_SET_BUFFERS_GEOMETRY:
|
||||
/* width */ va_arg(args, uint32_t);
|
||||
/* height */ va_arg(args, uint32_t);
|
||||
rs->mFormat = va_arg(args, PixelFormat);
|
||||
rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
|
||||
break;
|
||||
case ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT:
|
||||
rs->mFormat = va_arg(args, PixelFormat);
|
||||
rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <apex/window.h>
|
||||
#include <gui/Surface.h>
|
||||
#include <utils/Errors.h>
|
||||
#include <utils/Macros.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace android::uirenderer::renderthread {
|
||||
|
||||
@@ -29,7 +31,7 @@ class ReliableSurface {
|
||||
PREVENT_COPY_AND_ASSIGN(ReliableSurface);
|
||||
|
||||
public:
|
||||
ReliableSurface(sp<Surface>&& surface);
|
||||
ReliableSurface(ANativeWindow* window);
|
||||
~ReliableSurface();
|
||||
|
||||
// Performs initialization that is not safe to do in the constructor.
|
||||
@@ -37,12 +39,10 @@ public:
|
||||
// passed as the data pointer is not safe.
|
||||
void init();
|
||||
|
||||
ANativeWindow* getNativeWindow() { return mSurface.get(); }
|
||||
ANativeWindow* getNativeWindow() { return mWindow; }
|
||||
|
||||
int reserveNext();
|
||||
|
||||
int query(int what, int* value) const { return mSurface->query(what, value); }
|
||||
|
||||
int getAndClearError() {
|
||||
int ret = mBufferQueueState;
|
||||
mBufferQueueState = OK;
|
||||
@@ -50,12 +50,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
sp<Surface> mSurface;
|
||||
ANativeWindow* mWindow;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
|
||||
uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER;
|
||||
PixelFormat mFormat = PIXEL_FORMAT_RGBA_8888;
|
||||
AHardwareBuffer_Format mFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
|
||||
std::unique_ptr<AHardwareBuffer, void (*)(AHardwareBuffer*)> mScratchBuffer{
|
||||
nullptr, AHardwareBuffer_release};
|
||||
ANativeWindowBuffer* mReservedBuffer = nullptr;
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
#include "RenderProxy.h"
|
||||
|
||||
#include <gui/Surface.h>
|
||||
|
||||
#include "DeferredLayerUpdater.h"
|
||||
#include "DisplayList.h"
|
||||
#include "Properties.h"
|
||||
@@ -78,9 +76,11 @@ void RenderProxy::setName(const char* name) {
|
||||
mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
|
||||
}
|
||||
|
||||
void RenderProxy::setSurface(const sp<Surface>& surface, bool enableTimeout) {
|
||||
mRenderThread.queue().post([this, surf = surface, enableTimeout]() mutable {
|
||||
mContext->setSurface(std::move(surf), enableTimeout);
|
||||
void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
|
||||
ANativeWindow_acquire(window);
|
||||
mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
|
||||
mContext->setSurface(win, enableTimeout);
|
||||
ANativeWindow_release(win);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -314,10 +314,9 @@ void RenderProxy::setRenderAheadDepth(int renderAhead) {
|
||||
[context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); });
|
||||
}
|
||||
|
||||
int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom,
|
||||
int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom,
|
||||
SkBitmap* bitmap) {
|
||||
auto& thread = RenderThread::getInstance();
|
||||
ANativeWindow* window = surface.get();
|
||||
return static_cast<int>(thread.queue().runSync([&]() -> auto {
|
||||
return thread.readback().copySurfaceInto(window, Rect(left, top, right, bottom), bitmap);
|
||||
}));
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define RENDERPROXY_H_
|
||||
|
||||
#include <SkBitmap.h>
|
||||
#include <android/native_window.h>
|
||||
#include <cutils/compiler.h>
|
||||
#include <utils/Functor.h>
|
||||
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
ANDROID_API bool loadSystemProperties();
|
||||
ANDROID_API void setName(const char* name);
|
||||
|
||||
ANDROID_API void setSurface(const sp<Surface>& surface, bool enableTimeout = true);
|
||||
ANDROID_API void setSurface(ANativeWindow* window, bool enableTimeout = true);
|
||||
ANDROID_API void allocateBuffers();
|
||||
ANDROID_API bool pause();
|
||||
ANDROID_API void setStopped(bool stopped);
|
||||
@@ -140,11 +141,7 @@ public:
|
||||
*/
|
||||
ANDROID_API void setRenderAheadDepth(int renderAhead);
|
||||
|
||||
// TODO: This api will need to take in an ANativeWindow instead, but the
|
||||
// caller, ThreadedRenderer, doesn't have access to libandroid due to a
|
||||
// circular dependency, so it can't use the JNI ANativeWindow methods. Once
|
||||
// that is resolved then replace the surface type here.
|
||||
ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right,
|
||||
ANDROID_API static int copySurfaceInto(ANativeWindow* window, int left, int top, int right,
|
||||
int bottom, SkBitmap* bitmap);
|
||||
ANDROID_API static void prepareToDraw(Bitmap& bitmap);
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
magnifier->getSkBitmap(&temp);
|
||||
constexpr int x = 90;
|
||||
constexpr int y = 325;
|
||||
RenderProxy::copySurfaceInto(renderTarget, x, y, x + magnifier->width(),
|
||||
RenderProxy::copySurfaceInto(renderTarget.get(), x, y, x + magnifier->width(),
|
||||
y + magnifier->height(), &temp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ void run(const TestScene::Info& info, const TestScene::Options& opts,
|
||||
ContextFactory factory;
|
||||
std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode.get(), &factory));
|
||||
proxy->loadSystemProperties();
|
||||
proxy->setSurface(surface);
|
||||
proxy->setSurface(surface.get());
|
||||
float lightX = width / 2.0;
|
||||
proxy->setLightAlpha(255 * 0.075, 255 * 0.15);
|
||||
proxy->setLightGeometry((Vector3){lightX, dp(-200.0f), dp(800.0f)}, dp(800.0f));
|
||||
|
||||
@@ -210,7 +210,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscr
|
||||
auto surface = testContext.surface();
|
||||
int width = ANativeWindow_getWidth(surface.get());
|
||||
int height = ANativeWindow_getHeight(surface.get());
|
||||
canvasContext->setSurface(std::move(surface));
|
||||
canvasContext->setSurface(surface.get());
|
||||
|
||||
TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get());
|
||||
DamageAccumulator damageAccumulator;
|
||||
|
||||
Reference in New Issue
Block a user