From 426ae35a44f7a07c9338cd283a56c648cd1ba4b3 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Wed, 26 Mar 2025 19:39:33 +1000 Subject: [PATCH] start 0.5 --- build.gradle | 10 +++--- gradle.properties | 6 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- .../core/model/BakedBlockEntityModel.java | 13 +++++--- .../core/model/BudgetBufferRenderer.java | 17 ++++++++++ .../voxy/client/core/model/ModelFactory.java | 12 +++---- .../client/core/model/ModelTextureBakery.java | 32 ++++++++++--------- .../client/core/rendering/LightMapHelper.java | 3 +- .../core/rendering/VoxyRenderSystem.java | 2 +- .../voxy/common/world/other/Mapper.java | 8 ++--- .../commonImpl/importers/WorldImporter.java | 19 +++++------ src/main/resources/voxy.accesswidener | 6 ++-- 12 files changed, 76 insertions(+), 54 deletions(-) create mode 100644 src/main/java/me/cortex/voxy/client/core/model/BudgetBufferRenderer.java diff --git a/build.gradle b/build.gradle index 98437b08..0c4c2bc0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version "1.8.11" + id 'fabric-loom' version "1.10.1" id 'maven-publish' } @@ -74,17 +74,17 @@ dependencies { modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" //TODO: this is to eventually not need sodium installed as atm its just used for parsing shaders - modRuntimeOnly "maven.modrinth:sodium:mc1.21.4-0.6.10-fabric" - modCompileOnly "maven.modrinth:sodium:mc1.21.4-0.6.10-fabric" + modRuntimeOnly "maven.modrinth:sodium:mc1.21.5-0.6.12-fabric" + modCompileOnly "maven.modrinth:sodium:mc1.21.5-0.6.12-fabric" - modImplementation("maven.modrinth:lithium:mc1.21.4-0.14.7-fabric") + modImplementation("maven.modrinth:lithium:mc1.21.5-0.16.0-fabric") //modRuntimeOnly "maven.modrinth:nvidium:0.2.6-beta" modCompileOnly "maven.modrinth:nvidium:0.2.8-beta" modImplementation("maven.modrinth:cloth-config:17.0.144+fabric") - modImplementation("maven.modrinth:modmenu:13.0.0") + modImplementation("maven.modrinth:modmenu:14.0.0-rc.2") modCompileOnly("maven.modrinth:iris:1.8.5+1.21.4-fabric") diff --git a/gradle.properties b/gradle.properties index 04c8d81a..6af835c4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,12 +3,12 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -minecraft_version=1.21.4 -yarn_mappings=1.21.4+build.8 +minecraft_version=1.21.5 +yarn_mappings=1.21.5+build.1 loader_version=0.16.10 # Fabric API -fabric_version=0.114.2+1.21.4 +fabric_version=0.119.5+1.21.5 # Mod Properties mod_version = 0.2.0-alpha diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 21d5e095..18362b78 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/me/cortex/voxy/client/core/model/BakedBlockEntityModel.java b/src/main/java/me/cortex/voxy/client/core/model/BakedBlockEntityModel.java index 048e0550..912cee07 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/BakedBlockEntityModel.java +++ b/src/main/java/me/cortex/voxy/client/core/model/BakedBlockEntityModel.java @@ -1,12 +1,16 @@ package me.cortex.voxy.client.core.model; +import com.mojang.blaze3d.vertex.VertexFormat; +import me.cortex.voxy.common.Logger; import net.minecraft.block.BlockEntityProvider; import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.*; +import net.minecraft.client.texture.GlTexture; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; import java.util.ArrayList; import java.util.HashMap; @@ -115,11 +119,11 @@ public class BakedBlockEntityModel { System.err.println("ERROR: Empty texture id for layer: " + layer); } else { var texture = MinecraftClient.getInstance().getTextureManager().getTexture(textureId); - glBindTexture(GL_TEXTURE_2D, texture.getGlId()); + glBindTexture(GL_TEXTURE_2D, ((GlTexture)texture.getGlTexture()).getGlId()); } } layer.putInto(bb); - BufferRenderer.draw(bb.end()); + BudgetBufferRenderer.draw(bb.end()); } } } @@ -134,10 +138,9 @@ public class BakedBlockEntityModel { entity.setWorld(MinecraftClient.getInstance().world); if (renderer != null) { try { - renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, BakedVertices::new), 0, 0); + renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, BakedVertices::new), 0, 0, new Vec3d(0,0,0)); } catch (Exception e) { - System.err.println("Unable to bake block entity: " + entity); - e.printStackTrace(); + Logger.error("Unable to bake block entity: " + entity, e); } } entity.markRemoved(); diff --git a/src/main/java/me/cortex/voxy/client/core/model/BudgetBufferRenderer.java b/src/main/java/me/cortex/voxy/client/core/model/BudgetBufferRenderer.java new file mode 100644 index 00000000..79be6753 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/model/BudgetBufferRenderer.java @@ -0,0 +1,17 @@ +package me.cortex.voxy.client.core.model; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.gl.GlGpuBuffer; +import net.minecraft.client.render.BuiltBuffer; + +import static org.lwjgl.opengl.GL15C.*; + +public class BudgetBufferRenderer { + public static void draw(BuiltBuffer buffer) { + var params = buffer.getDrawParameters(); + try (var gpuBuf = params.format().uploadImmediateIndexBuffer(buffer.getBuffer())) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ((GlGpuBuffer)RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS).getIndexBuffer(params.indexCount())).id); + } + + } +} diff --git a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java index ebffd847..6be643f0 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java +++ b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java @@ -1,7 +1,5 @@ package me.cortex.voxy.client.core.model; -import com.mojang.blaze3d.platform.GlConst; -import com.mojang.blaze3d.platform.GlStateManager; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -597,7 +595,7 @@ public class ModelFactory { private float[] computeModelDepth(ColourDepthTextureData[] textures, int checkMode) { float[] res = new float[6]; for (var dir : Direction.values()) { - var data = textures[dir.getId()]; + var data = textures[dir.getIndex()]; float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_AVG, checkMode);//Compute the min float depth, smaller means closer to the camera, range 0-1 int depth = Math.round(fd * MODEL_TEXTURE_SIZE); //If fd is -1, it means that there was nothing rendered on that face and it should be discarded @@ -642,10 +640,10 @@ public class ModelFactory { int x = X + (subTex>>1)*MODEL_TEXTURE_SIZE; int y = Y + (subTex&1)*MODEL_TEXTURE_SIZE; - GlStateManager._pixelStore(GlConst.GL_UNPACK_ROW_LENGTH, 0); - GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_PIXELS, 0); - GlStateManager._pixelStore(GlConst.GL_UNPACK_SKIP_ROWS, 0); - GlStateManager._pixelStore(GlConst.GL_UNPACK_ALIGNMENT, 4); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); var current = textures[subTex].colour(); var next = new int[current.length>>1]; final int layers = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE); diff --git a/src/main/java/me/cortex/voxy/client/core/model/ModelTextureBakery.java b/src/main/java/me/cortex/voxy/client/core/model/ModelTextureBakery.java index e49d5330..97914240 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/ModelTextureBakery.java +++ b/src/main/java/me/cortex/voxy/client/core/model/ModelTextureBakery.java @@ -1,6 +1,6 @@ package me.cortex.voxy.client.core.model; -import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.vertex.VertexFormat; import me.cortex.voxy.client.core.gl.GlFramebuffer; import me.cortex.voxy.client.core.gl.GlTexture; import me.cortex.voxy.client.core.gl.shader.Shader; @@ -11,7 +11,7 @@ import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gl.GlUniform; import net.minecraft.client.render.*; -import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BlockStateModel; import net.minecraft.client.util.BufferAllocator; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.fluid.FluidState; @@ -188,7 +188,7 @@ public class ModelTextureBakery { glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilMask(0xFF); - int texId = MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlId(); + int texId = ((net.minecraft.client.texture.GlTexture)MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlTexture()).getGlId(); final int TEXTURE_SIZE = this.width*this.height *4;//NOTE! assume here that both depth and colour are 4 bytes in size for (int i = 0; i < FACE_VIEWS.size(); i++) { @@ -217,10 +217,10 @@ public class ModelTextureBakery { } private final BufferAllocator allocator = new BufferAllocator(786432); - private void captureViewToStream(BlockState state, BakedModel model, BakedBlockEntityModel blockEntityModel, MatrixStack stack, long randomValue, int face, boolean renderFluid, int textureId, Matrix4f projection, int streamBuffer, int streamOffset) { + private void captureViewToStream(BlockState state, BlockStateModel model, BakedBlockEntityModel blockEntityModel, MatrixStack stack, long randomValue, int face, boolean renderFluid, int textureId, Matrix4f projection, int streamBuffer, int streamOffset) { this.rasterShader.bind(); glActiveTexture(GL_TEXTURE0); - GlUniform.uniform1(0, 0); + glUniform1i(0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); float[] mat = new float[4*4]; @@ -251,7 +251,7 @@ public class ModelTextureBakery { if (!renderFluid) { //TODO: need to do 2 variants for quads, one which have coloured, ones that dont, might be able to pull a spare bit // at the end whether or not a pixel should be mixed with texture - renderQuads(bb, state, model, new MatrixStack(), randomValue); + renderQuads(bb, model, new MatrixStack(), randomValue); } else { MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() { @Override @@ -282,7 +282,7 @@ public class ModelTextureBakery { @Override public BlockState getBlockState(BlockPos pos) { - if (pos.equals(Direction.byId(face).getVector())) { + if (pos.equals(Direction.byIndex(face).getVector())) { return Blocks.AIR.getDefaultState(); } @@ -300,7 +300,7 @@ public class ModelTextureBakery { @Override public FluidState getFluidState(BlockPos pos) { - if (pos.equals(Direction.byId(face).getVector())) { + if (pos.equals(Direction.byIndex(face).getVector())) { return Blocks.AIR.getDefaultState().getFluidState(); } //if (pos.getY() == 1) { @@ -324,7 +324,7 @@ public class ModelTextureBakery { glBindTexture(GL_TEXTURE_2D, textureId); try { //System.err.println("REPLACE THE UPLOADING WITH THREAD SAFE VARIENT"); - BufferRenderer.draw(bb.end()); + BudgetBufferRenderer.draw(bb.end()); } catch (IllegalStateException e) { //System.err.println("Got empty buffer builder! for block " + state); } @@ -356,13 +356,15 @@ public class ModelTextureBakery { glDispatchCompute(1,1,1); } - private static void renderQuads(BufferBuilder builder, BlockState state, BakedModel model, MatrixStack stack, long randomValue) { + private static void renderQuads(BufferBuilder builder, BlockStateModel model, MatrixStack stack, long randomValue) { for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) { - var quads = model.getQuads(state, direction, new LocalRandom(randomValue)); - for (var quad : quads) { - //TODO: mark pixels that have - int meta = 1; - builder.quad(stack.peek(), quad, ((meta>>16)&0xff)/255f, ((meta>>8)&0xff)/255f, (meta&0xff)/255f, 1.0f, 0, 0); + for (var part : model.getParts(new LocalRandom(randomValue))) { + var quads = part.getQuads(direction); + for (var quad : quads) { + //TODO: mark pixels that have + int meta = 1; + builder.quad(stack.peek(), quad, ((meta >> 16) & 0xff) / 255f, ((meta >> 8) & 0xff) / 255f, (meta & 0xff) / 255f, 1.0f, 0, 0); + } } } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/LightMapHelper.java b/src/main/java/me/cortex/voxy/client/core/rendering/LightMapHelper.java index d71cf35f..1eafcf07 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/LightMapHelper.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/LightMapHelper.java @@ -1,6 +1,7 @@ package me.cortex.voxy.client.core.rendering; import me.cortex.voxy.client.core.gl.GlBuffer; +import me.cortex.voxy.client.core.gl.GlTexture; import me.cortex.voxy.client.core.rendering.util.UploadStream; import net.minecraft.client.MinecraftClient; import org.lwjgl.system.MemoryUtil; @@ -36,6 +37,6 @@ public class LightMapHelper { public static void bind(int lightingIndex) { //glBindBufferBase(GL_SHADER_STORAGE_BUFFER, lightingBufferIndex, LIGHT_MAP_BUFFER.id); //glBindSampler(lightingIndex, SAMPLER); - glBindTextureUnit(lightingIndex, MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().lightmapFramebuffer.getColorAttachment()); + glBindTextureUnit(lightingIndex, ((net.minecraft.client.texture.GlTexture)(MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().getGlTexture())).getGlId()); } } \ No newline at end of file diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java b/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java index faa6ae48..c11b7436 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/VoxyRenderSystem.java @@ -52,7 +52,7 @@ public class VoxyRenderSystem { var client = MinecraftClient.getInstance(); var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true); - float fov = gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickDelta(true), true); + float fov = gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickProgress(true), true); projection.setPerspective(fov * 0.01745329238474369f, (float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(), diff --git a/src/main/java/me/cortex/voxy/common/world/other/Mapper.java b/src/main/java/me/cortex/voxy/common/world/other/Mapper.java index 80010bee..160ae8e4 100644 --- a/src/main/java/me/cortex/voxy/common/world/other/Mapper.java +++ b/src/main/java/me/cortex/voxy/common/world/other/Mapper.java @@ -313,10 +313,10 @@ public class Mapper { public static StateEntry deserialize(int id, byte[] data) { try { var compound = NbtIo.readCompressed(new ByteArrayInputStream(data), NbtSizeTracker.ofUnlimitedBytes()); - if (compound.getInt("id") != id) { + if (compound.getInt("id", -1) != id) { throw new IllegalStateException("Encoded id != expected id"); } - BlockState state = BlockState.CODEC.parse(NbtOps.INSTANCE, compound.getCompound("block_state")).getOrThrow(); + BlockState state = BlockState.CODEC.parse(NbtOps.INSTANCE, compound.getCompound("block_state").orElseThrow()).getOrThrow(); return new StateEntry(id, state); } catch (IOException e) { throw new RuntimeException(e); @@ -349,10 +349,10 @@ public class Mapper { public static BiomeEntry deserialize(int id, byte[] data) { try { var compound = NbtIo.readCompressed(new ByteArrayInputStream(data), NbtSizeTracker.ofUnlimitedBytes()); - if (compound.getInt("id") != id) { + if (compound.getInt("id", -1) != id) { throw new IllegalStateException("Encoded id != expected id"); } - String biome = compound.getString("biome_id"); + String biome = compound.getString("biome_id", null); return new BiomeEntry(id, biome); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/main/java/me/cortex/voxy/commonImpl/importers/WorldImporter.java b/src/main/java/me/cortex/voxy/commonImpl/importers/WorldImporter.java index fb2d386c..f34d6f7f 100644 --- a/src/main/java/me/cortex/voxy/commonImpl/importers/WorldImporter.java +++ b/src/main/java/me/cortex/voxy/commonImpl/importers/WorldImporter.java @@ -396,22 +396,22 @@ public class WorldImporter implements IDataImporter { } //Dont process non full chunk sections - var status = ChunkStatus.byId(chunk.getString("Status")); + var status = ChunkStatus.byId(chunk.getString("Status", null)); if (status != ChunkStatus.FULL && status != ChunkStatus.EMPTY) {//We also import empty since they are from data upgrade this.totalChunks.decrementAndGet(); return; } try { - int x = chunk.getInt("xPos"); - int z = chunk.getInt("zPos"); + int x = chunk.getInt("xPos", Integer.MIN_VALUE); + int z = chunk.getInt("zPos", Integer.MIN_VALUE); if (x>>5 != regionX || z>>5 != regionZ) { Logger.error("Chunk position is not located in correct region, expected: (" + regionX + ", " + regionZ+"), got: " + "(" + (x>>5) + ", " + (z>>5)+"), importing anyway"); } - for (var sectionE : chunk.getList("sections", NbtElement.COMPOUND_TYPE)) { + for (var sectionE : chunk.getList("sections").orElseThrow()) { var section = (NbtCompound) sectionE; - int y = section.getInt("Y"); + int y = section.getInt("Y", Integer.MIN_VALUE); this.importSectionNBT(x, y, z, section); } } catch (Exception e) { @@ -421,6 +421,7 @@ public class WorldImporter implements IDataImporter { this.updateCallback.onUpdate(this.chunksProcessed.incrementAndGet(), this.estimatedTotalChunks.get()); } + private static final byte[] EMPTY = new byte[0]; private static final ThreadLocal SECTION_CACHE = ThreadLocal.withInitial(VoxelizedSection::createEmpty); private static final Codec> BLOCK_STATE_CODEC = PalettedContainer.createPalettedContainerCodec(Block.STATE_IDS, BlockState.CODEC, PalettedContainer.PaletteProvider.BLOCK_STATE, Blocks.AIR.getDefaultState()); private void importSectionNBT(int x, int y, int z, NbtCompound section) { @@ -428,8 +429,8 @@ public class WorldImporter implements IDataImporter { return; } - byte[] blockLightData = section.getByteArray("BlockLight"); - byte[] skyLightData = section.getByteArray("SkyLight"); + byte[] blockLightData = section.getByteArray("BlockLight").orElse(EMPTY); + byte[] skyLightData = section.getByteArray("SkyLight").orElse(EMPTY); ChunkNibbleArray blockLight; if (blockLightData.length != 0) { @@ -445,13 +446,13 @@ public class WorldImporter implements IDataImporter { skyLight = null; } - var blockStatesRes = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, section.getCompound("block_states")); + var blockStatesRes = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, section.getCompound("block_states").get()); if (!blockStatesRes.hasResultOrPartial()) { //TODO: if its only partial, it means should try to upgrade the nbt format with datafixerupper probably return; } var blockStates = blockStatesRes.getPartialOrThrow(); - var biomes = this.biomeCodec.parse(NbtOps.INSTANCE, section.getCompound("biomes")).result().orElse(this.defaultBiomeProvider); + var biomes = this.biomeCodec.parse(NbtOps.INSTANCE, section.getCompound("biomes").get()).result().orElse(this.defaultBiomeProvider); VoxelizedSection csec = WorldConversionFactory.convert( SECTION_CACHE.get().setPosition(x, y, z), diff --git a/src/main/resources/voxy.accesswidener b/src/main/resources/voxy.accesswidener index 11da1d0b..aaf665c0 100644 --- a/src/main/resources/voxy.accesswidener +++ b/src/main/resources/voxy.accesswidener @@ -21,8 +21,8 @@ accessible field net/minecraft/client/network/ClientPlayerInteractionManager net accessible method net/minecraft/client/render/GameRenderer getFov (Lnet/minecraft/client/render/Camera;FZ)F accessible method net/minecraft/client/render/RenderPhase$TextureBase getId ()Ljava/util/Optional; -accessible field net/minecraft/client/render/LightmapTextureManager lightmapFramebuffer Lnet/minecraft/client/gl/SimpleFramebuffer; - accessible field net/minecraft/world/chunk/PalettedContainer data Lnet/minecraft/world/chunk/PalettedContainer$Data; accessible field net/minecraft/world/chunk/PalettedContainer$Data storage Lnet/minecraft/util/collection/PaletteStorage; -accessible field net/minecraft/world/chunk/PalettedContainer$Data palette Lnet/minecraft/world/chunk/Palette; \ No newline at end of file +accessible field net/minecraft/world/chunk/PalettedContainer$Data palette Lnet/minecraft/world/chunk/Palette; + +accessible field net/minecraft/client/gl/GlGpuBuffer id I \ No newline at end of file