mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-11-10 12:34:11 +01:00
Merge branch '1.18/dev' into 1.18/next
# Conflicts: # .github/ISSUE_TEMPLATE/bug_report.yml # build.gradle # gradle.properties # src/main/java/com/jozufozu/flywheel/api/Material.java # src/main/java/com/jozufozu/flywheel/backend/gl/GlStateTracker.java # src/main/java/com/jozufozu/flywheel/backend/gl/buffer/MappedBuffer.java # src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java # src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchedMaterial.java # src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchedMaterialGroup.java # src/main/java/com/jozufozu/flywheel/backend/instancing/batching/BatchingEngine.java # src/main/java/com/jozufozu/flywheel/backend/instancing/batching/CPUInstancer.java # src/main/java/com/jozufozu/flywheel/backend/instancing/entity/EntityInstanceManager.java # src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/ElementBuffer.java # src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancer.java # src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterial.java # src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedMaterialGroup.java # src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java # src/main/java/com/jozufozu/flywheel/backend/model/ArrayModelRenderer.java # src/main/java/com/jozufozu/flywheel/backend/model/FallbackAllocator.java # src/main/java/com/jozufozu/flywheel/backend/model/IndexedModel.java # src/main/java/com/jozufozu/flywheel/backend/model/ModelPool.java # src/main/java/com/jozufozu/flywheel/backend/model/VBOModel.java # src/main/java/com/jozufozu/flywheel/core/QuadConverter.java # src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingGroup.java # src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java # src/main/java/com/jozufozu/flywheel/core/hardcoded/ModelPart.java # src/main/java/com/jozufozu/flywheel/core/model/BlockModel.java # src/main/java/com/jozufozu/flywheel/core/model/Model.java # src/main/java/com/jozufozu/flywheel/core/model/WorldModelBuilder.java # src/main/java/com/jozufozu/flywheel/core/vertex/AbstractVertexList.java # src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexList.java # src/main/java/com/jozufozu/flywheel/event/ForgeEvents.java # src/main/java/com/jozufozu/flywheel/mixin/ClientMainMixin.java # src/main/java/com/jozufozu/flywheel/mixin/LevelRendererMixin.java # src/main/resources/flywheel.mixins.json
This commit is contained in:
commit
3dd74fe4b2
@ -13,6 +13,17 @@ insert_final_newline = true
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = 500
|
||||
ij_json_keep_blank_lines_in_code = 0
|
||||
ij_json_keep_indents_on_empty_lines = false
|
||||
ij_json_keep_line_breaks = true
|
||||
ij_json_space_after_colon = true
|
||||
ij_json_space_after_comma = true
|
||||
ij_json_space_before_colon = true
|
||||
ij_json_space_before_comma = false
|
||||
ij_json_spaces_within_braces = true
|
||||
ij_json_spaces_within_brackets = false
|
||||
ij_json_wrap_long_lines = false
|
||||
|
||||
[*.java]
|
||||
indent_style = tab
|
||||
|
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -60,6 +60,10 @@ body:
|
||||
description: The version of the mod you were using when the bug occured
|
||||
options:
|
||||
- "0.7.0"
|
||||
- "0.6.8.a"
|
||||
- "0.6.8"
|
||||
- "0.6.7"
|
||||
- "0.6.6"
|
||||
- "0.6.5"
|
||||
- "0.6.4"
|
||||
- "0.6.3"
|
||||
@ -91,6 +95,7 @@ body:
|
||||
label: Minecraft Version
|
||||
description: The version of Minecraft you were using when the bug occured
|
||||
options:
|
||||
- "1.19.2"
|
||||
- "1.18.2"
|
||||
- "1.18.1"
|
||||
- "1.18"
|
||||
|
@ -1,3 +1,28 @@
|
||||
0.6.8:
|
||||
Fixes
|
||||
- Fix colored vertices being incorrectly rendered through instancing
|
||||
- Fix some miscellaneous bugs with sodium/rubidium
|
||||
- Fix memory leak associated with model storage
|
||||
Technical/API
|
||||
- Memory for models is freed when they are no longer in use
|
||||
- Element buffers now deal in raw gl handles
|
||||
- Element buffers are no longer considered part of state resoration
|
||||
- Quad -> Tri element buffer properly resets itself
|
||||
|
||||
0.6.7:
|
||||
...is 1.19 only :ioa:
|
||||
|
||||
0.6.6:
|
||||
Fixes
|
||||
- Fix instanced entities not rendering correctly when chunk loading is slow
|
||||
Technical/API
|
||||
- GL state is more reliably managed
|
||||
- Slight memory improvements when using Iris/Oculus through lazy instatiation
|
||||
|
||||
0.6.5:
|
||||
Fixes
|
||||
- Fix crash with batching backend and Rubidium
|
||||
|
||||
0.6.4:
|
||||
Fixes
|
||||
- Fix shader detection with oculus
|
||||
|
@ -14,7 +14,7 @@ mixingradle_version = 0.7-SNAPSHOT
|
||||
mixin_version = 0.8.5
|
||||
librarian_version = 1.+
|
||||
cursegradle_version = 1.4.0
|
||||
parchment_version = 2022.07.10
|
||||
parchment_version = 2022.11.06
|
||||
|
||||
# curseforge info
|
||||
projectId = 486392
|
||||
|
@ -48,7 +48,7 @@ public class GlStateTracker {
|
||||
GlBufferType[] values = GlBufferType.values();
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (buffers[i] != GlStateTracker.BUFFERS[i]) {
|
||||
if (buffers[i] != GlStateTracker.BUFFERS[i] && values[i] != GlBufferType.ELEMENT_ARRAY_BUFFER) {
|
||||
GlStateManager._glBindBuffer(values[i].glEnum, buffers[i]);
|
||||
}
|
||||
}
|
||||
|
@ -137,12 +137,11 @@ public class GlVertexArray extends GlObject {
|
||||
}
|
||||
}
|
||||
|
||||
public void bindElementArray(GlBuffer ebo) {
|
||||
int handle = ebo.handle();
|
||||
if (elementBufferBinding != handle) {
|
||||
public void bindElementArray(int ebo) {
|
||||
if (elementBufferBinding != ebo) {
|
||||
bind();
|
||||
GlBufferType.ELEMENT_ARRAY_BUFFER.bind(handle);
|
||||
elementBufferBinding = handle;
|
||||
GlBufferType.ELEMENT_ARRAY_BUFFER.bind(ebo);
|
||||
elementBufferBinding = ebo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package com.jozufozu.flywheel.backend.instancing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -261,4 +263,14 @@ public abstract class InstanceManager<T> {
|
||||
LightUpdater.get(value.level).removeListener(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void queueAddAll(Collection<? extends T> objects) {
|
||||
if (!Backend.isOn() || objects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (queuedAdditions) {
|
||||
queuedAdditions.addAll(objects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,7 @@ import com.jozufozu.flywheel.backend.instancing.InstanceManager;
|
||||
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
|
||||
import com.jozufozu.flywheel.backend.instancing.One2OneStorage;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public class EntityInstanceManager extends InstanceManager<Entity> {
|
||||
@ -44,14 +42,6 @@ public class EntityInstanceManager extends InstanceManager<Entity> {
|
||||
|
||||
Level level = entity.level;
|
||||
|
||||
if (Backend.isFlywheelLevel(level)) {
|
||||
BlockPos pos = entity.blockPosition();
|
||||
|
||||
BlockGetter existingChunk = level.getChunkForCollisions(pos.getX() >> 4, pos.getZ() >> 4);
|
||||
|
||||
return existingChunk != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
return Backend.isFlywheelLevel(level);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public class IndirectCullingGroup<T extends InstancedPart> {
|
||||
vertexArray = glCreateVertexArrays();
|
||||
|
||||
elementBuffer = QuadConverter.getInstance()
|
||||
.quads2Tris(2048).buffer.handle();
|
||||
.quads2Tris(2048).glBuffer;
|
||||
setupVertexArray();
|
||||
|
||||
compute = ComputeCullerCompiler.INSTANCE.get(structType);
|
||||
|
@ -1,17 +1,29 @@
|
||||
package com.jozufozu.flywheel.backend.instancing.instancing;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.GlNumericType;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
|
||||
public class ElementBuffer {
|
||||
|
||||
public final GlBuffer buffer;
|
||||
public final int elementCount;
|
||||
public final GlNumericType eboIndexType;
|
||||
protected final int elementCount;
|
||||
protected final VertexFormat.IndexType eboIndexType;
|
||||
public final int glBuffer;
|
||||
|
||||
public ElementBuffer(GlBuffer backing, int elementCount, GlNumericType indexType) {
|
||||
this.buffer = backing;
|
||||
this.eboIndexType = indexType;
|
||||
public ElementBuffer(int backing, int elementCount, VertexFormat.IndexType indexType) {
|
||||
this.elementCount = elementCount;
|
||||
this.eboIndexType = indexType;
|
||||
this.glBuffer = backing;
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
GlBufferType.ELEMENT_ARRAY_BUFFER.bind(glBuffer);
|
||||
}
|
||||
|
||||
public int getElementCount() {
|
||||
return elementCount;
|
||||
}
|
||||
|
||||
public VertexFormat.IndexType getEboIndexType() {
|
||||
return eboIndexType;
|
||||
}
|
||||
}
|
||||
|
@ -228,15 +228,15 @@ public class InstancedMeshPool {
|
||||
vao.enableArrays(getAttributeCount());
|
||||
vao.bindAttributes(InstancedMeshPool.this.vbo, 0, vertexType.getLayout(), byteIndex);
|
||||
}
|
||||
vao.bindElementArray(ebo.buffer);
|
||||
vao.bindElementArray(ebo.glBuffer);
|
||||
vao.bind();
|
||||
}
|
||||
|
||||
private void draw(int instanceCount) {
|
||||
if (instanceCount > 1) {
|
||||
GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0, instanceCount);
|
||||
GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.asGLType, 0, instanceCount);
|
||||
} else {
|
||||
GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0);
|
||||
GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, ebo.elementCount, ebo.eboIndexType.asGLType, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,11 +27,11 @@ public class InstancingDrawManager {
|
||||
private final Map<InstancerKey<?>, GPUInstancer<?>> instancers = new HashMap<>();
|
||||
private final List<UninitializedInstancer> uninitializedInstancers = new ArrayList<>();
|
||||
private final List<GPUInstancer<?>> initializedInstancers = new ArrayList<>();
|
||||
private final Map<RenderStage, DrawSet> drawDets = new EnumMap<>(RenderStage.class);
|
||||
private final Map<RenderStage, DrawSet> drawSets = new EnumMap<>(RenderStage.class);
|
||||
private final Map<VertexType, InstancedMeshPool> meshPools = new HashMap<>();
|
||||
|
||||
public DrawSet get(RenderStage stage) {
|
||||
return drawDets.getOrDefault(stage, DrawSet.EMPTY);
|
||||
return drawSets.getOrDefault(stage, DrawSet.EMPTY);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -67,9 +67,9 @@ public class InstancingDrawManager {
|
||||
.forEach(InstancedMeshPool::delete);
|
||||
meshPools.clear();
|
||||
|
||||
drawDets.values()
|
||||
drawSets.values()
|
||||
.forEach(DrawSet::delete);
|
||||
drawDets.clear();
|
||||
drawSets.clear();
|
||||
|
||||
initializedInstancers.forEach(GPUInstancer::delete);
|
||||
initializedInstancers.clear();
|
||||
@ -80,7 +80,7 @@ public class InstancingDrawManager {
|
||||
}
|
||||
|
||||
private void add(GPUInstancer<?> instancer, Model model, RenderStage stage) {
|
||||
DrawSet drawSet = drawDets.computeIfAbsent(stage, DrawSet::new);
|
||||
DrawSet drawSet = drawSets.computeIfAbsent(stage, DrawSet::new);
|
||||
var meshes = model.getMeshes();
|
||||
for (var entry : meshes.entrySet()) {
|
||||
DrawCall drawCall = new DrawCall(instancer, entry.getKey(), alloc(entry.getValue()));
|
||||
|
@ -16,12 +16,12 @@ import net.minecraftforge.client.model.ForgeModelBakery;
|
||||
* A helper class for loading and accessing json models.
|
||||
* <br>
|
||||
* Creating a PartialModel will make the associated modelLocation automatically load.
|
||||
* PartialModels must be initialized during {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent FMLClientSetupEvent}.
|
||||
* PartialModels must be initialized the mod class constructor.
|
||||
* <br>
|
||||
* Once {@link ModelBakeEvent} finishes, all PartialModels (with valid modelLocations)
|
||||
* will have their bakedModel fields populated.
|
||||
* <br>
|
||||
* Attempting to create a PartialModel after ModelRegistryEvent will cause an error.
|
||||
* Attempting to create a PartialModel after {@link ModelRegistryEvent} will cause an error.
|
||||
*/
|
||||
public class PartialModel {
|
||||
|
||||
|
@ -2,14 +2,19 @@ package com.jozufozu.flywheel.core;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL32C;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.GlNumericType;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
|
||||
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferUsage;
|
||||
import com.jozufozu.flywheel.backend.instancing.instancing.ElementBuffer;
|
||||
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
|
||||
|
||||
/**
|
||||
* A class to manage EBOs that index quads as triangles.
|
||||
@ -32,56 +37,71 @@ public class QuadConverter {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final GlBuffer ebo;
|
||||
private final Int2ReferenceMap<ElementBuffer> cache = new Int2ReferenceArrayMap<>();
|
||||
private final int ebo;
|
||||
private int quadCapacity;
|
||||
|
||||
public QuadConverter() {
|
||||
this.ebo = new GlBuffer(GlBufferType.ELEMENT_ARRAY_BUFFER);
|
||||
this.ebo = GL32.glGenBuffers();
|
||||
this.quadCapacity = 0;
|
||||
}
|
||||
|
||||
public ElementBuffer quads2Tris(int quads) {
|
||||
int indexCount = quads * 6;
|
||||
|
||||
if (quads > quadCapacity) {
|
||||
ebo.ensureCapacity((long) indexCount * GlNumericType.UINT.getByteWidth());
|
||||
|
||||
try (MappedBuffer map = ebo.map()) {
|
||||
fillBuffer(map.getPtr(), quads);
|
||||
}
|
||||
ebo.unbind();
|
||||
|
||||
this.quadCapacity = quads;
|
||||
grow(quads * 2);
|
||||
}
|
||||
|
||||
return new ElementBuffer(ebo, indexCount, GlNumericType.UINT);
|
||||
return cache.computeIfAbsent(quads, this::createElementBuffer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ElementBuffer createElementBuffer(int quads) {
|
||||
return new ElementBuffer(ebo, quads * 6, VertexFormat.IndexType.INT);
|
||||
}
|
||||
|
||||
private void grow(int quads) {
|
||||
int byteSize = quads * 6 * GlNumericType.UINT.getByteWidth();
|
||||
final long ptr = MemoryUtil.nmemAlloc(byteSize);
|
||||
|
||||
fillBuffer(ptr, quads);
|
||||
|
||||
final var bufferType = GlBufferType.ARRAY_BUFFER;
|
||||
final int oldBuffer = bufferType.getBoundBuffer();
|
||||
bufferType.bind(ebo);
|
||||
GL32C.nglBufferData(bufferType.glEnum, byteSize, ptr, GlBufferUsage.STATIC_DRAW.glEnum);
|
||||
bufferType.bind(oldBuffer);
|
||||
|
||||
MemoryUtil.nmemFree(ptr);
|
||||
|
||||
this.quadCapacity = quads;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
ebo.delete();
|
||||
GL32.glDeleteBuffers(ebo);
|
||||
this.cache.clear();
|
||||
this.quadCapacity = 0;
|
||||
}
|
||||
|
||||
private void fillBuffer(long addr, int quads) {
|
||||
private void fillBuffer(long ptr, int quads) {
|
||||
int numVertices = 4 * quads;
|
||||
int baseVertex = 0;
|
||||
while (baseVertex < numVertices) {
|
||||
writeQuadIndices(addr, baseVertex);
|
||||
writeQuadIndicesUnsafe(ptr, baseVertex);
|
||||
|
||||
baseVertex += 4;
|
||||
addr += 6 * 4;
|
||||
ptr += 6 * 4;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeQuadIndices(long addr, int baseVertex) {
|
||||
private void writeQuadIndicesUnsafe(long ptr, int baseVertex) {
|
||||
// triangle a
|
||||
MemoryUtil.memPutInt(addr, baseVertex);
|
||||
MemoryUtil.memPutInt(addr + 4, baseVertex + 1);
|
||||
MemoryUtil.memPutInt(addr + 8, baseVertex + 2);
|
||||
MemoryUtil.memPutInt(ptr, baseVertex);
|
||||
MemoryUtil.memPutInt(ptr + 4, baseVertex + 1);
|
||||
MemoryUtil.memPutInt(ptr + 8, baseVertex + 2);
|
||||
// triangle b
|
||||
MemoryUtil.memPutInt(addr + 12, baseVertex);
|
||||
MemoryUtil.memPutInt(addr + 16, baseVertex + 2);
|
||||
MemoryUtil.memPutInt(addr + 20, baseVertex + 3);
|
||||
MemoryUtil.memPutInt(ptr + 12, baseVertex);
|
||||
MemoryUtil.memPutInt(ptr + 16, baseVertex + 2);
|
||||
MemoryUtil.memPutInt(ptr + 20, baseVertex + 3);
|
||||
}
|
||||
|
||||
// make sure this gets reset first, so it has a chance to repopulate
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.jozufozu.flywheel.mixin;
|
||||
package com.jozufozu.flywheel.mixin.instancemanage;
|
||||
|
||||
import java.util.Set;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.jozufozu.flywheel.mixin;
|
||||
package com.jozufozu.flywheel.mixin.instancemanage;
|
||||
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
@ -1,4 +1,4 @@
|
||||
package com.jozufozu.flywheel.mixin;
|
||||
package com.jozufozu.flywheel.mixin.instancemanage;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
@ -10,7 +10,7 @@ version = "${file.jarVersion}"
|
||||
displayName = "Flywheel"
|
||||
logoFile = "logo.png"
|
||||
displayURL = "https://www.curseforge.com/minecraft/mc-mods/flywheel"
|
||||
updateJSONURL = "https://api.modrinth.com/updates/5lpsZoRi/forge_updates.json"
|
||||
updateJSONURL = "https://api.modrinth.com/updates/flywheel/forge_updates.json"
|
||||
authors = "Jozufozu"
|
||||
description = '''
|
||||
A modern engine for modded minecraft.'''
|
||||
|
@ -8,21 +8,21 @@
|
||||
"BlockEntityRenderDispatcherAccessor",
|
||||
"BlockEntityTypeMixin",
|
||||
"BufferBuilderMixin",
|
||||
"ChunkRebuildHooksMixin",
|
||||
"ClientLevelMixin",
|
||||
"ClientMainMixin",
|
||||
"EntityTypeMixin",
|
||||
"FixFabulousDepthMixin",
|
||||
"FogUpdateMixin",
|
||||
"GlStateManagerMixin",
|
||||
"InstanceAddMixin",
|
||||
"InstanceRemoveMixin",
|
||||
"LevelRendererAccessor",
|
||||
"LevelRendererInstanceUpdateMixin",
|
||||
"LevelRendererMixin",
|
||||
"PausedPartialTickAccessor",
|
||||
"RenderTypeMixin",
|
||||
"VertexFormatMixin",
|
||||
"instancemanage.ChunkRebuildHooksMixin",
|
||||
"instancemanage.InstanceAddMixin",
|
||||
"instancemanage.InstanceRemoveMixin",
|
||||
"light.LightUpdateMixin",
|
||||
"light.NetworkLightUpdateMixin",
|
||||
"matrix.Matrix3fAccessor",
|
||||
|
Loading…
Reference in New Issue
Block a user