From c6b1c06a3f9e3ab82b7cbf04c33592a2c2a1f90d Mon Sep 17 00:00:00 2001
From: thatmidcoder393 <73651803+ThatMG393@users.noreply.github.com>
Date: Sun, 15 Dec 2024 14:10:14 +0800
Subject: [PATCH] Upstreamed/combine draw commands (#8)
* rename some things for clarity
* fix waterlogged glass panes (once again, but more this time) by avoiding distance sorting through
the detection of primary intersectors when geometry is intersecting and then sorting them in a fixed order
* use Mth.clamp for clarity
* refactor buffer and sort result handling, buffers are now freed immediately instead of keeping them to avoid memory usage
buffer caching would be a better solution but that's complicated and doesn't currently work correctly
* reduce number of unique triggers by around 5 percent without impacting sorting or building performance
* importantly sort a little farther away, sort tasks are fast
* use defer zero frames for important sort tasks by default
* fix build
* clarify authorship of BitArray
* fix bug with radix sort for SNR heuristic in BSP partition generating wrong indexes
* combine draw commands
* correctly reset accumulated element count
* remove draw call combining for indexed rendering as it's broken and hard to fix
* skip heuristic if there's no quads
* refactor primary intersector detection to handle large cases better,
also removed the warning message about unpartitionable geometry as it seems to not be a relevant problem
* fix topo sorting in some situations where the dot product was wrongly not recalculated when the normal is quantized.
also fixed aligned quads not receiving the more accurate center based on the average of the unique vertexes.
* reorder vertex ranges before uploaded to optimize for combined draw commands
* tune primary intersector detection to handle situations where only a small amount of geometry is intersecting
* Correctly handle colorization on NeoForge
* Combine the vertex position attributes (#2753)
This improves terrain rendering performance significantly
on Intel Xe-LP graphics under Linux.
* Add option for Fullscreen Resolution (#2642)
The resolution controls would not fit in the allocated space, so the
rendering of slider controls was changed to enable rendering the slider
bar and the value text on separate lines.
Co-authored-by: MeeniMc <68366846+MeeniMc@users.noreply.github.com>
* Only enable Fullscreen Resolution option on Windows
Additionally, adjust the rendering of the controls
to be less confusing when disabled, and provide an
explanation as to what the option does.
* fix draw command combining, remove aggressive non-empty command skipping because it seems broken
* Use consistent vertex ordering in entity rendering
Some core shaders were relying on the model part faces being
written out in a specific order. We still don't support
core shaders, but the fix here is trivial enough.
Fixes #2745
* Add check for NeoForge per-quad AO flag
* Disable mod entrypoint on Forge when running on servers (#2773)
* fix graphical corruption when there's a lot of geometry by appropriately picking the size of the required shared index buffer
* cleanup unused and broken code
* cleanup calculation of mask bit and element count
* cleanup meshing, storage, and renderer
* fix translucent rendering by correctly decoding vertex segments
* cleanup misc, remove unused code
* refactor translucent AnyOrderData to not generate its own trivial index buffer and instead share this type of data within regions
* add Index Pool arena size
* add arena content caching
* Fix excessively large allocations in chunk meshing
The requested capacity was being multiplied by the vertex
stride more than once, which resulted in far too much
memory being allocated.
Closes #2792
* Fix some issues with Uint32 representation
This increases the maximum size of vertex and index buffers
to 4 billion elements, since the Uint32 types stored in memory are
now safely represented with Int64.
For vertex buffers, this increases their maximum size to 80 GiB,
and index buffers have a maximum size of 16 GiB, whereas both
were limited to 2 GiB prior.
* refactor storage to cope with larger amounts of geometry and use less ugly hacks, rename a bunch of methods to be consistent and clearer
* remove debug code
* Fix cull bitmask ordering in entity rendering
Closes #2788
* Add support for Maven Local publishing
* Fix incorrect warning message when D3DKMT is not supported
* Add Flawless Frames handler for NeoForge
* Add angle-based section visibility path occlusion (#2811)
This eliminates 8-13% of the rendered sections at higher render distances on average in testing, and correspondingly reduces graph search time by a similar amount.
* Disable material downgrading on Intel Gen8 and older
Fixes #2830
* Delay normal face calculation to use
This potentially fixes some cases of #2835.
* Skip particle rendering optimizations for incompatible mods
Fixes #2827
* Update project URLs in source and documentation
We're no longer a Fabric-exclusive mod, so let's get rid of
the suffix.
* Add third-party license notice for Fabric API
* Optimizations for some block models (#2508)
Co-authored-by: muzikbike <52297970+muzikbike@users.noreply.github.com>
* Fix sorting failures on rotated cuboids (#2812)
Use the accurate vertex positions for unaligned and aligned (but rotated) quads.
* Rework the Gradle build scripts for multi-loader
* Shared logic is moved into a build plugin where possible
* Build time is significantly improved when the Gradle daemon is warmed
* Mixins are remapped in-place now, eliminating the need for refmaps
at runtime. This also gets rid of some warning messages at startup.
* Module relationships are now correctly represented in IDEA for other
source sets (fixes a lot of code analysis features)
* Split Java source and resources into different configurations
* Run configurations are now consistent between NeoForge/Fabric
* The common project is no longer remapped unnecessarily
* Updated Gradle and build plugins
* Restore versioning schema to build script
* Make organization of platform mixin packages consistent
Fixes #2688
* Exclude README documentation from processed resources
These files are only meant to be in the source distribution, and
Minecraft doesn't like them.
* Don't try to load a refmap from the mixin plugin
* Enumerate additional PCI classes in the graphics adapter probe
Some integrated GPUs, such as RDNA3.5, appear to use
the PCI_CLASS_DISPLAY_OTHER class.
* Remove KDE and GNOME specific backends for browsing URLs
The bugs with xdg-open have been resolved upstream and most
Linux distributions are shipping the patches.
Also, make sure we get a successful exit code from the XDG
implementation.
* Remove Minecraft from classpath of the pre-launch source set
This will help to avoid class-load issues and makes the code more
hygienic.
* Update to Minecraft 1.21.3
* Ensure tooltips are constrained to the screen (#2845)
* Update authors and contributors list
* Remove leftover popPose
* Trust existing fog color
* Block the Overwolf Overlay due to graphical corruption
The overlay does not correctly restore the texture unit state
in OpenGL, which causes problems when Minecraft thinks a texture
has already been bound to a slot.
Since disabling the OpenGL state cache globally is not an
acceptable solution (it would severely hurt performance) and
their software doesn't give us any method to detect the
problematic version, we block all versions.
gep_minecraft.dll is the payload they actually inject, which
has no version information or description.
Fixes #2862
* Avoid static memory allocation in EntityRenderer
Just allocate on the stack, since it's a small amount of
memory (<1 KiB) and avoids needing complex finalizers.
* Fix y-offset calculation for back face culling in cloud rendering (#2864)
* Fix memory leak and double free in CloudRenderer
* Improve color mixing functions
The existing functions did not implement rounding
correctly (often leading to off-by-one errors).
Additionally, the improved variants are both slightly
faster and easier to understand.
* Fixup documentation in ColorMixer
* Unify color mixing/swizzling utilities
The Fabric integration code was re-implementing a lot
of the utilities that already exist in Sodium unnecessarily.
Also, improve the documentation so that ABGR and RGBA are
not used interchangeably.
* Add optimized function for bi-linear interpolation
This reduces the number of ALU ops significantly and
creates a common utility function in the project.
* Reduce time complexity for box blurs
Measuring the time spent per box blur in biome blending,
the following results were observed.
Radius Before After % Improvement
7 blocks 9100ns 3700ns 59%
3 blocks 5400ns 3200ns 41%
1 blocks 3700ns 2600ns 29%
* Revert detection of Overwolf Overlay
They claim this has since been fixed. We will re-examine in
the future if we see additional reports.
This reverts commit e7ea6f7dd5207c8fd0dac584a1134a16025a85de.
* Bump version to 0.6.0-beta.5
* Bump version to 0.6.0-final
* Update render code for chunk status map
Fixes #2881
* Rollup of fixes and improvements for cloud rendering
Some changes were made to cloud rendering in newer versions
that needed to be replicated in Sodium.
- The alpha cutoff for clouds was changed to (a < 10).
- Texture loading can now gracefully fail, and it is
expected that rendering is skipped when this happens.
- The movement/positioning of clouds was slightly changed.
- The render pass system now needs to be told about
render target usages (fixes #2883).
This commit also improves mesh building time by around 35% on
a fast processor (AMD Ryzen HX AI 370) through various
micro-optimizations.
* Fix culling behavior between transparent and opaque blocks
Minecraft 1.21.2 changed some of the rules, and this was
causing the faces of transparent blocks to be rendered
even when they were hidden by full opaque blocks.
Fixes #2850
* Fix precision issues in cloud rendering at far distances
* Use alternative workaround for NVIDIA drivers
The NVIDIA driver enables a driver feature called
"Threaded Optimizations" when it finds Minecraft,
which causes severe performance issues and sometimes
even crashes.
Newer versions of the driver seem to use a slightly
different heuristic which our workaround doesn't
address.
So, instead, use an alternative method that enables
GL_DEBUG_OUTPUT_SYNCHRONOUS. This seems to reliably
disable the functionality *even if* the user has
configured it otherwise in their driver settings.
Additionally, on Windows, we now always indicate to
the driver that Minecraft is running, so that users
with hybrid graphics don't see regressed performance.
* Sort render lists for regions and sections after traversal (#2780)
Render sections and regions are sorted after the graph traversal is performed. This decouples their ordering from the graph, which isn't entirely correct for draw call sorting.
Fixes #2266
* Add support for new NeoForge fluid overlay API
* Ensure depth test is configured when rendering clouds
The state of the depth test prior to cloud rendering is
undefined. After rendering, it is expected to be
disabled again.
* Fix rounding error in ColorMixer#mix
Rounding of the values now happens after the 16-bit
intermediaries are added together.
This affected some animated textures, causing them to
exhibit flickering behavior.
* Add additional optimized block models
This covers the following additional blocks:
- Cauldrons
- Brewing Stands
- Bells
Co-authored-by: JellySquid
* Avoid marking the section graph as dirty if state didn't change (#2886)
Avoids rebuilding the render lists and doing a graph search
more often than necessary by checking if the section actually
changed in a way that's relevant to the graph search.
For worlds that update their blocks frequently (every tick or
every redstone tick) this avoids half the graph searches. Some
graph searches are still necessary to schedule rebuild tasks,
but when the task results come back, this doesn't do another
graph search unless the section's visibility data or build state
changed in a way that needs the render list to be updated.
* Use larger bounding box for nearby sections in frustum check (#2879)
This fixes some problems where very large block entities in
nearby sections may be incorrectly culled. But it does not
comprehensively fix the problem for all other sections,
since that would require visiting the 27-neighborhood of
every section, which is too slow.
* Bump version to 0.6.1
* Bump dependency versions
* Update mod manifest
* Ensure ItemRenderContext.isDefaultTranslucent is initialized
* Update compatible mods listing
* Update mod manifest to restrict Minecraft versions
* Update to Minecraft 1.21.4
* Update NeoForge manifest for Minecraft 1.21.4
* Fix glyph effect orientation
* Fix hidden surface elimination in fluid rendering for waterlogged blocks (#2907)
* Fix detection for specific Intel OpenGL ICDs
The OpenGL ICD name now includes the file extension,
which the regex expressions were not matching.
* Avoid showing the incompatible driver error in some cases
For systems with hybrid graphics, it may be the case
that an incompatible graphics driver is installed, but that
it isn't used for the OpenGL context.
We can avoid showing errors in this situation by checking
the vendor string of the context immediately after
creation.
This is not the most robust check, but in practice, a single
system should not have multiple graphics drivers installed
from the same vendor, so checking the string should be
relatively safe.
* Fix lambda mappings
* Bump version and dependency requirements
* Use correct coordinates for sorting chunk sections (#2924)
Fix section and region sorting by using the correct section coordinate instead of the integer part of the camera transform, which is incorrect near the origin.
Closes #2918
* Update README.md
---------
Co-authored-by: douira
Co-authored-by: IMS212
Co-authored-by: JellySquid <1363084+jellysquid3@users.noreply.github.com>
Co-authored-by: douira
Co-authored-by: MeeniMc <68366846+MeeniMc@users.noreply.github.com>
Co-authored-by: JellySquid
Co-authored-by: IThundxr
Co-authored-by: muzikbike <52297970+muzikbike@users.noreply.github.com>
---
.github/ISSUE_TEMPLATE/bug_report.yml | 4 +-
.github/ISSUE_TEMPLATE/config.yml | 2 +-
.github/ISSUE_TEMPLATE/feature_request.yml | 2 +-
.github/workflows/build-commit.yml | 2 +-
.github/workflows/build-pull-request.yml | 2 +-
.github/workflows/build-release.yml | 3 +-
.github/workflows/build-tag.yml | 2 +-
README.md | 85 +-
build.gradle.kts | 80 --
buildSrc/build.gradle.kts | 8 +
buildSrc/src/main/kotlin/BuildConfig.kt | 41 +
.../main/kotlin/multiloader-base.gradle.kts | 44 +
.../kotlin/multiloader-platform.gradle.kts | 43 +
common/build.gradle.kts | 155 ++--
.../mods/sodium/api/util/ColorABGR.java | 58 +-
.../mods/sodium/api/util/ColorARGB.java | 55 +-
.../mods/sodium/api/util/ColorMixer.java | 187 +++--
.../api/vertex/buffer/VertexBufferWriter.java | 2 +-
.../mods/sodium/desktop/LaunchWarn.java | 2 +-
.../utils/browse/BrowseUrlHandler.java | 9 -
.../desktop/utils/browse/GNOMEImpl.java | 16 -
.../sodium/desktop/utils/browse/KDEImpl.java | 16 -
.../sodium/desktop/utils/browse/XDGImpl.java | 15 +-
.../client/checks/ResourcePackScanner.java | 6 +-
.../checks/SodiumResourcePackMetadata.java | 2 +-
.../client/data/config/MixinConfig.java | 4 +-
.../sodium/client/gl/arena/GlBufferArena.java | 94 ++-
.../client/gl/arena/GlBufferSegment.java | 75 +-
.../gl/arena/PendingBufferCopyCommand.java | 35 +-
.../client/gl/buffer/IndexedVertexData.java | 16 -
.../client/gl/device/MultiDrawBatch.java | 1 +
.../gl/shader/uniform/GlUniformFloat4v.java | 4 +
.../client/gui/SodiumGameOptionPages.java | 44 +-
.../sodium/client/gui/SodiumOptionsGUI.java | 15 +-
.../sodium/client/gui/options/OptionFlag.java | 1 +
.../sodium/client/gui/options/OptionImpl.java | 16 +-
.../gui/options/control/ControlElement.java | 51 +-
.../control/ControlValueFormatter.java | 16 +
.../gui/options/control/SliderControl.java | 64 +-
.../client/gui/widgets/AbstractWidget.java | 3 +
.../client/gui/widgets/FlatButtonWidget.java | 5 +
.../model/color/DefaultColorProviders.java | 4 +-
.../color/interop/ItemColorsExtension.java | 8 -
.../model/light/data/LightDataAccess.java | 4 +-
.../client/model/quad/BakedQuadView.java | 2 +
.../sodium/client/model/quad/ModelQuad.java | 15 +-
.../client/model/quad/ModelQuadView.java | 13 +-
.../model/quad/ModelQuadViewMutable.java | 4 +-
.../quad/blender/BlendedColorProvider.java | 85 +-
.../client/render/SodiumWorldRenderer.java | 14 +-
.../render/chunk/DefaultChunkRenderer.java | 124 +--
.../client/render/chunk/RenderSection.java | 30 +-
.../render/chunk/RenderSectionManager.java | 52 +-
.../render/chunk/ShaderChunkRenderer.java | 5 +-
.../render/chunk/SharedQuadIndexBuffer.java | 9 +
.../chunk/compile/ChunkBuildBuffers.java | 91 +-
.../render/chunk/compile/ChunkSortOutput.java | 36 +-
.../chunk/compile/executor/ChunkBuilder.java | 13 +-
.../compile/pipeline/BlockOcclusionCache.java | 129 ++-
.../chunk/compile/pipeline/BlockRenderer.java | 25 +-
.../pipeline/DefaultFluidRenderer.java | 55 +-
.../tasks/ChunkBuilderMeshingTask.java | 33 +-
.../tasks/ChunkBuilderSortingTask.java | 8 +
.../chunk/data/BuiltSectionMeshParts.java | 19 +-
.../chunk/data/SectionRenderDataStorage.java | 159 +++-
.../chunk/data/SectionRenderDataUnsafe.java | 69 +-
.../render/chunk/lists/ChunkRenderList.java | 40 +-
.../render/chunk/lists/SortedRenderLists.java | 42 -
.../chunk/lists/VisibleChunkCollector.java | 65 +-
.../chunk/occlusion/OcclusionCuller.java | 96 ++-
.../chunk/occlusion/VisibilityEncoding.java | 2 +-
.../render/chunk/region/RenderRegion.java | 28 +-
.../chunk/region/RenderRegionManager.java | 65 +-
.../shader/ChunkShaderBindingPoints.java | 9 +-
.../chunk/shader/ChunkShaderFogComponent.java | 8 +-
.../chunk/translucent_sorting/SortType.java | 13 +-
.../chunk/translucent_sorting/TQuad.java | 74 +-
.../TranslucentGeometryCollector.java | 47 +-
.../translucent_sorting/bsp_tree/BSPNode.java | 2 +-
.../bsp_tree/InnerPartitionBSPNode.java | 4 +-
.../data/AnyOrderData.java | 16 +-
.../data/DynamicSorter.java | 2 +-
.../data/DynamicTopoData.java | 1 -
.../data/PresentSorter.java | 23 +
.../data/SharedIndexSorter.java | 27 +
.../translucent_sorting/data/SortData.java | 5 -
.../translucent_sorting/data/Sorter.java | 17 +-
.../data/StaticNormalRelativeData.java | 9 +-
.../data/StaticSorter.java | 2 +-
.../data/TopoGraphSorting.java | 99 ++-
.../trigger/GeometryPlanes.java | 4 +-
.../builder/ChunkMeshBufferBuilder.java | 52 +-
.../format/impl/CompactChunkVertex.java | 7 +-
.../impl/DefaultChunkMeshAttributes.java | 3 +-
.../client/render/frapi/SodiumRenderer.java | 10 +-
.../render/frapi/helper/ColorHelper.java | 70 +-
.../render/frapi/helper/GeometryHelper.java | 5 +-
.../render/frapi/helper/NormalHelper.java | 2 +-
.../frapi/material/MaterialFinderImpl.java | 13 +-
.../frapi/material/MaterialViewImpl.java | 28 +-
.../frapi/material/RenderMaterialImpl.java | 9 +
.../render/frapi/mesh/EncodingFormat.java | 3 +-
.../render/frapi/mesh/MeshBuilderImpl.java | 84 --
.../client/render/frapi/mesh/MeshImpl.java | 45 +-
.../render/frapi/mesh/MutableMeshImpl.java | 93 +++
.../frapi/mesh/MutableQuadViewImpl.java | 117 ++-
.../render/frapi/mesh/QuadViewImpl.java | 13 +-
.../render/AbstractBlockRenderContext.java | 91 +-
.../frapi/render/AbstractRenderContext.java | 65 +-
.../frapi/render/ItemRenderContext.java | 261 ++----
.../render/NonTerrainBlockRenderContext.java | 18 +-
.../render/immediate/CloudRenderer.java | 789 +++++++++++-------
.../immediate/model/BakedModelEncoder.java | 17 +-
.../immediate/model/EntityRenderer.java | 44 +-
.../render/immediate/model/ModelCuboid.java | 36 +-
.../client/services/PlatformBlockAccess.java | 9 +
.../mods/sodium/client/util/UInt32.java | 22 +
.../util/WeightedRandomListExtension.java | 7 +
.../client/util/collections/BitArray.java | 4 +-
.../sodium/client/util/color/BoxBlur.java | 91 +-
.../sodium/client/util/color/ColorSRGB.java | 5 +-
.../mods/sodium/client/world/LevelSlice.java | 4 +-
.../client/world/biome/BiomeColorMaps.java | 2 +-
.../client/world/biome/LevelBiomeSlice.java | 4 +-
.../client/world/biome/LevelColorCache.java | 42 +-
.../world/cloned/ClonedChunkSection.java | 2 +-
.../mods/sodium/mixin/SodiumMixinPlugin.java | 2 +-
.../sodium/mixin/core/MinecraftMixin.java | 3 +-
.../mods/sodium/mixin/core/WindowMixin.java | 25 +-
.../core/model/colors/ItemColorsMixin.java | 33 -
.../SheetedDecalTextureGeneratorMixin.java | 2 +-
.../render/world/EntityRendererAccessor.java | 13 +
.../core/render/world/LevelRendererMixin.java | 13 +-
.../core/world/biome/ClientLevelMixin.java | 3 +-
.../core/world/map/ClientChunkCacheMixin.java | 2 +-
.../world/map/ClientPacketListenerMixin.java | 2 +-
.../gui/hooks/console/GameRendererMixin.java | 10 +-
.../gui/screen/LevelLoadingScreenMixin.java | 69 +-
.../options/weather/LevelRendererMixin.java | 5 +-
.../render/compositing/RenderTargetMixin.java | 26 -
.../entity/cull/EntityRendererMixin.java | 5 +-
.../render/frapi/BakedModelMixin.java | 24 +-
.../render/frapi/ItemRendererAccessor.java | 9 +-
.../render/frapi/ItemRendererMixin.java | 21 +-
.../render/gui/font/BakedGlyphMixin.java | 58 +-
.../gui/outlines/LevelRendererMixin.java | 3 +-
.../render/immediate/DirectionMixin.java | 2 +-
.../intrinsics/BufferBuilderMixin.java | 2 +-
.../render/model/item/ItemRendererMixin.java | 38 +-
.../particle/SingleQuadParticleMixin.java | 47 +-
.../world/clouds/LevelRendererMixin.java | 47 +-
.../render/world/sky/FogRendererMixin.java | 2 +-
.../render/world/sky/LevelRendererMixin.java | 11 +-
.../shader/uniform/ShaderInstanceMixin.java | 45 +-
.../animations/tracking/GuiGraphicsMixin.java | 19 +-
.../tracking/SpriteContentsTickerMixin.java | 2 +-
.../SpriteContentsInterpolationMixin.java | 4 +-
.../mipmaps/MipmapGeneratorMixin.java | 30 +-
.../textures/mipmaps/SpriteContentsMixin.java | 13 +-
.../context_creation/WindowMixin.java | 53 +-
.../assets/minecraft/models/block/README.txt | 8 +
.../assets/minecraft/models/block/beacon.json | 46 +
.../minecraft/models/block/bell_floor.json | 43 +
.../minecraft/models/block/brewing_stand.json | 57 ++
.../minecraft/models/block/cauldron.json | 214 +++++
.../minecraft/models/block/composter.json | 85 ++
.../minecraft/models/block/flower_pot.json | 88 ++
.../models/block/flower_pot_cross.json | 108 +++
.../minecraft/models/block/heavy_core.json | 39 +
.../assets/minecraft/models/block/hopper.json | 107 +++
.../minecraft/models/block/hopper_side.json | 107 +++
.../minecraft/models/block/inner_stairs.json | 61 ++
.../minecraft/models/block/lightning_rod.json | 42 +
.../models/block/lightning_rod_on.json | 33 +
.../block/mangrove_propagule_hanging_0.json | 75 ++
.../block/mangrove_propagule_hanging_1.json | 85 ++
.../block/mangrove_propagule_hanging_2.json | 103 +++
.../block/mangrove_propagule_hanging_3.json | 103 +++
.../block/mangrove_propagule_hanging_4.json | 103 +++
.../minecraft/models/block/potted_bamboo.json | 110 +++
.../block/potted_mangrove_propagule.json | 129 +++
.../assets/minecraft/models/block/stairs.json | 62 ++
.../models/block/template_anvil.json | 60 ++
.../block/template_cake_with_candle.json | 51 ++
.../models/block/template_cauldron_full.json | 221 +++++
.../block/template_cauldron_level1.json | 221 +++++
.../block/template_cauldron_level2.json | 221 +++++
.../models/block/template_chorus_flower.json | 46 +
.../block/template_four_turtle_eggs.json | 57 ++
.../block/template_hanging_lantern.json | 51 ++
.../models/block/template_item_frame.json | 85 ++
.../models/block/template_item_frame_map.json | 57 ++
.../block/template_potted_azalea_bush.json | 146 ++++
.../models/block/tinted_flower_pot_cross.json | 108 +++
.../assets/minecraft/shaders/core/clouds.json | 23 -
.../resources/assets/sodium/lang/en_us.json | 1 +
.../core => sodium/shaders}/clouds.fsh | 0
.../assets/sodium/shaders/clouds.json | 21 +
.../core => sodium/shaders}/clouds.vsh | 0
.../sodium/shaders/include/chunk_vertex.glsl | 11 +-
.../resources/sodium-common.accesswidener | 21 +
....mixins.json => sodium-common.mixins.json} | 2 +-
.../checks/GraphicsDriverChecks.java | 63 ++
.../compatibility/checks/ModuleScanner.java | 37 +-
.../checks/PostLaunchChecks.java | 14 +-
.../compatibility/checks/PreLaunchChecks.java | 191 +----
.../environment/GLContextInfo.java | 20 -
.../environment/GlContextInfo.java | 19 +
.../compatibility/environment/OsUtils.java | 27 +-
.../probe/GraphicsAdapterProbe.java | 75 +-
.../probe/GraphicsAdapterVendor.java | 65 +-
.../workarounds/Workarounds.java | 68 +-
.../workarounds/intel/IntelWorkarounds.java | 59 ++
.../workarounds/nvidia/NvidiaWorkarounds.java | 155 +++-
.../sodium/client/platform/MessageBox.java | 35 +-
.../client/platform/NativeWindowHandle.java | 5 +
.../client/platform/PlatformHelper.java | 29 +
.../client/platform/windows/api/Gdi32.java | 18 +-
.../platform/windows/api/d3dkmt/D3DKMT.java | 38 +-
fabric/build.gradle.kts | 96 +--
.../mods/sodium/fabric/SodiumFabricMod.java | 4 +-
.../mods/sodium/fabric/SodiumPreLaunch.java | 3 +-
.../fabric/block/FabricBlockAccess.java | 5 +
.../mixin/core/model/quad/BakedQuadMixin.java | 26 +-
.../model/MultiPartBakedModelMixin.java | 10 +-
.../model/WeightedBakedModelMixin.java | 35 +-
.../model/WeightedRandomListMixin.java | 44 +
.../model/block/ModelBlockRendererMixin.java | 4 +-
.../features/world/biome/BiomeMixin.java | 2 +-
fabric/src/main/resources/fabric.mod.json | 80 +-
.../resources/sodium-fabric.accesswidener | 0
.../main/resources/sodium-fabric.mixins.json | 14 +-
gradle/wrapper/gradle-wrapper.properties | 2 +-
neoforge/build.gradle.kts | 180 ++--
.../core/model/quad/BakedQuadMixin.java | 42 +-
.../model/MultiPartBakedModelMixin.java | 25 +-
.../model/WeightedBakedModelMixin.java | 45 +-
.../model/WeightedRandomListMixin.java | 44 +
.../model/block/ModelBlockRendererMixin.java | 4 +-
.../features/world/biome/BiomeMixin.java | 2 +-
.../AbstractBlockRenderContextMixin.java | 37 +
.../neoforge}/AuxiliaryLightManagerMixin.java | 2 +-
.../neoforge}/ChunkRenderTypeSetAccessor.java | 2 +-
.../platform/neoforge}/ClientHooksMixin.java | 2 +-
.../platform/neoforge}/EntrypointMixin.java | 2 +-
.../platform/neoforge}/LevelSliceMixin.java | 3 +-
.../platform/neoforge}/ModelDataMixin.java | 2 +-
.../neoforge}/ResourcePackLoaderMixin.java | 2 +-
.../neoforge}/SimpleBakedModelAccessor.java | 2 +-
.../neoforge/NeoForgeRuntimeInformation.java | 3 +-
.../mods/sodium/neoforge/SodiumForgeMod.java | 32 +-
.../neoforge/block/NeoForgeBlockAccess.java | 10 +-
.../AbstractBlockRenderContextMixin.java | 28 -
.../resources/META-INF/neoforge.mods.toml | 19 +-
.../main/resources/sodium-forge.mixins.json | 28 -
.../resources/sodium-neoforge.mixins.json | 28 +
.../sodium/service/SodiumWorkarounds.java | 10 +-
settings.gradle.kts | 1 +
thirdparty/NOTICE.txt | 9 +-
thirdparty/licenses/LICENSE-APACHE-2.0.txt | 202 +++++
260 files changed, 7865 insertions(+), 3056 deletions(-)
create mode 100644 buildSrc/build.gradle.kts
create mode 100644 buildSrc/src/main/kotlin/BuildConfig.kt
create mode 100644 buildSrc/src/main/kotlin/multiloader-base.gradle.kts
create mode 100644 buildSrc/src/main/kotlin/multiloader-platform.gradle.kts
delete mode 100644 common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/GNOMEImpl.java
delete mode 100644 common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/KDEImpl.java
delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java
delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/interop/ItemColorsExtension.java
create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java
create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java
delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java
delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java
create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableMeshImpl.java
create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/UInt32.java
create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/WeightedRandomListExtension.java
delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/colors/ItemColorsMixin.java
create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/world/EntityRendererAccessor.java
create mode 100644 common/src/main/resources/assets/minecraft/models/block/README.txt
create mode 100644 common/src/main/resources/assets/minecraft/models/block/beacon.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/bell_floor.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/brewing_stand.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/cauldron.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/composter.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/flower_pot.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/flower_pot_cross.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/heavy_core.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/hopper.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/hopper_side.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/inner_stairs.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/lightning_rod.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/lightning_rod_on.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/mangrove_propagule_hanging_0.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/mangrove_propagule_hanging_1.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/mangrove_propagule_hanging_2.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/mangrove_propagule_hanging_3.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/mangrove_propagule_hanging_4.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/potted_bamboo.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/potted_mangrove_propagule.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/stairs.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_anvil.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_cake_with_candle.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_chorus_flower.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_four_turtle_eggs.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_hanging_lantern.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_item_frame.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_item_frame_map.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_potted_azalea_bush.json
create mode 100644 common/src/main/resources/assets/minecraft/models/block/tinted_flower_pot_cross.json
delete mode 100644 common/src/main/resources/assets/minecraft/shaders/core/clouds.json
rename common/src/main/resources/assets/{minecraft/shaders/core => sodium/shaders}/clouds.fsh (100%)
create mode 100644 common/src/main/resources/assets/sodium/shaders/clouds.json
rename common/src/main/resources/assets/{minecraft/shaders/core => sodium/shaders}/clouds.vsh (100%)
create mode 100644 common/src/main/resources/sodium-common.accesswidener
rename common/src/main/resources/{sodium.mixins.json => sodium-common.mixins.json} (98%)
create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java
delete mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GLContextInfo.java
create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java
create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java
create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/NativeWindowHandle.java
create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java
rename {neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge => fabric/src/main/java/net/caffeinemc/mods/sodium}/mixin/core/model/quad/BakedQuadMixin.java (85%)
rename fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/{fabric => }/features/model/MultiPartBakedModelMixin.java (86%)
rename fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/{fabric => }/features/model/WeightedBakedModelMixin.java (54%)
create mode 100644 fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/model/WeightedRandomListMixin.java
rename fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/{fabric => }/features/render/model/block/ModelBlockRendererMixin.java (97%)
rename fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/{fabric => }/features/world/biome/BiomeMixin.java (97%)
rename common/src/main/resources/sodium.accesswidener => fabric/src/main/resources/sodium-fabric.accesswidener (100%)
rename {fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/fabric => neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin}/core/model/quad/BakedQuadMixin.java (74%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge => }/mixin/features/model/MultiPartBakedModelMixin.java (82%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge => }/mixin/features/model/WeightedBakedModelMixin.java (51%)
create mode 100644 neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/model/WeightedRandomListMixin.java
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge => }/mixin/features/render/model/block/ModelBlockRendererMixin.java (97%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge => }/mixin/features/world/biome/BiomeMixin.java (97%)
create mode 100644 neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/AuxiliaryLightManagerMixin.java (83%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/ChunkRenderTypeSetAccessor.java (88%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/ClientHooksMixin.java (92%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/EntrypointMixin.java (94%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/LevelSliceMixin.java (95%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/ModelDataMixin.java (80%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/ResourcePackLoaderMixin.java (96%)
rename neoforge/src/main/java/net/caffeinemc/mods/sodium/{neoforge/mixin => mixin/platform/neoforge}/SimpleBakedModelAccessor.java (85%)
delete mode 100644 neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/mixin/AbstractBlockRenderContextMixin.java
delete mode 100644 neoforge/src/main/resources/sodium-forge.mixins.json
create mode 100644 neoforge/src/main/resources/sodium-neoforge.mixins.json
create mode 100644 thirdparty/licenses/LICENSE-APACHE-2.0.txt
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index a80e3ed44b..d01ccf8aa3 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -17,10 +17,10 @@ body:
- **Have you ensured that all of your mods (including Sodium) are up-to-date?** The latest version of Sodium
can always be found [on Modrinth](https://modrinth.com/mod/sodium).
- - **Have you read the [list of known driver incompatibilities](https://github.com/CaffeineMC/sodium-fabric/wiki/Driver-Compatibility)?** Most problems
+ - **Have you read the [list of known driver incompatibilities](https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility)?** Most problems
(including "poor performance") are caused by out-of-date or incompatible graphics drivers.
- - **Have you used the [search tool](https://github.com/CaffeineMC/sodium-fabric/issues) to check whether your issue
+ - **Have you used the [search tool](https://github.com/CaffeineMC/sodium/issues) to check whether your issue
has already been reported?** If it has been, then consider adding more information to the existing issue instead.
- **Have you determined the minimum set of instructions to reproduce the issue?** If your problem only occurs
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 1abfdc6fba..5097a676da 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -3,4 +3,4 @@ contact_links:
- name: For help with other issues, join our Discord community
url: https://caffeinemc.net/discord
about: This is the best option for getting help with mod installation, performance issues, and any other support inquiries
- # Copied from https://github.com/CaffeineMC/sodium-fabric#community
+ # Copied from https://github.com/CaffeineMC/sodium#community
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 0c88dc3550..991291427e 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -17,7 +17,7 @@ body:
- type: markdown
attributes:
value: >-
- Make sure you have used the [search tool](https://github.com/CaffeineMC/sodium-fabric/issues) to see if a similar
+ Make sure you have used the [search tool](https://github.com/CaffeineMC/sodium/issues) to see if a similar
request already exists. If we have previously closed a feature request, then please do not create another request.
- type: textarea
id: description
diff --git a/.github/workflows/build-commit.yml b/.github/workflows/build-commit.yml
index 7b4621a81b..b73702a561 100644
--- a/.github/workflows/build-commit.yml
+++ b/.github/workflows/build-commit.yml
@@ -38,4 +38,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: sodium-artifacts-${{ steps.ref.outputs.branch }}
- path: build/libs/*.jar
\ No newline at end of file
+ path: build/mods/*.jar
\ No newline at end of file
diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml
index 1cf8d3e7d7..045dd53920 100644
--- a/.github/workflows/build-pull-request.yml
+++ b/.github/workflows/build-pull-request.yml
@@ -28,4 +28,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: sodium-artifacts-${{ steps.ref.outputs.branch }}
- path: build/libs/*.jar
\ No newline at end of file
+ path: build/mods/*.jar
\ No newline at end of file
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index ebf1d9ea13..ab751d49b9 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -28,6 +28,5 @@ jobs:
- name: Upload assets to GitHub
uses: AButler/upload-release-assets@v2.0
with:
- # Filter built files to disregard -sources and -dev, and leave only the minecraft-compatible jars.
- files: 'build/libs/*[0-9].jar;LICENSE'
+ files: 'build/mods/*.jar'
repo-token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/build-tag.yml b/.github/workflows/build-tag.yml
index 4ac8946fd9..478c3b3d2b 100644
--- a/.github/workflows/build-tag.yml
+++ b/.github/workflows/build-tag.yml
@@ -36,4 +36,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: sodium-artifacts-${{ steps.ref.outputs.branch }}
- path: build/libs/*.jar
\ No newline at end of file
+ path: build/mods/*.jar
\ No newline at end of file
diff --git a/README.md b/README.md
index 995884d091..e8af77f8ca 100644
--- a/README.md
+++ b/README.md
@@ -5,25 +5,53 @@
Sodium is a powerful rendering engine and optimization mod for the Minecraft client which improves frame rates and reduces
micro-stutter, while fixing many graphical issues in Minecraft.
-### 📥 Installation
+**This mod is the result of thousands of hours of development, and is made possible thanks to players like you.** If you
+would like to show a token of your appreciation for my work, and help support the development of Sodium in the process,
+then consider [buying me a coffee](https://caffeinemc.net/donate).
-The latest version of Sodium can be downloaded from our official [Modrinth](https://modrinth.com/mod/sodium) and
-[CurseForge](https://www.curseforge.com/minecraft/mc-mods/sodium) pages.
+
+
+---
+
+### 📥 Downloads
+
+#### Stable builds
+
+The latest stable release of Sodium can be downloaded from our official [Modrinth](https://modrinth.com/mod/sodium) and
+[CurseForge](https://www.curseforge.com/minecraft/mc-mods/sodium) pages.
+
+#### Nightly builds (for developers)
+
+We also provide bleeding-edge builds ("nightlies") which are useful for testing the very latest changes before they're
+packaged into a release. These builds are only provided for other mod developers and users with expert skills, and do
+not come with any support or warranty. It is often the case they have issues and lack compatibility with other mods.
+
+The latest nightly build for each current branch of development can be downloaded below.
+
+- Minecraft 1.21.4 (latest): [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/dev/sodium-artifacts-dev.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3Adev)
+- Minecraft 1.21.3: [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/1.21.3%2Fstable/sodium-artifacts-1.21.3-stable.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3A1.21.3%2Fstable)
+- Minecraft 1.21.1: [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/1.21.1%2Fstable/sodium-artifacts-1.21.1-stable.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3A1.21.1%2Fstable)
+
+### 🖥️ Installation
Since the release of Sodium 0.6.0, both the _Fabric_ and _NeoForge_ mod loaders are supported. We generally recommend
that new users prefer to use the _Fabric_ mod loader, since it is more lightweight and stable (for the time being.)
-For more information about downloading and installing the mod, please refer to our [Installation Guide](https://github.com/CaffeineMC/sodium-fabric/wiki/Installation).
+For more information about downloading and installing the mod, please refer to our [Installation Guide](https://github.com/CaffeineMC/sodium/wiki/Installation).
-### 🐛 Reporting Issues
+### 🙇 Getting Help
-You can report bugs and crashes by opening an issue on our [issue tracker](https://github.com/CaffeineMC/sodium-fabric/issues).
-Before opening a new issue, use the search tool to make sure that your issue has not already been reported and ensure
-that you have completely filled out the issue template. Issues that are duplicates or do not contain the necessary
-information to triage and debug may be closed.
+For technical support (including help with mod installation problems and game crashes), please use our
+[official Discord server](https://caffeinemc.net/discord).
+
+### 📬 Reporting Issues
+
+If you do not need technical support and would like to report an issue (bug, crash, etc.) or otherwise request changes
+(for mod compatibility, new features, etc.), then we encourage you to open an issue on the
+[project issue tracker](https://github.com/CaffeineMC/sodium/issues).
Please note that while the issue tracker is open to feature requests, development is primarily focused on
-improving hardware compatibility, performance, and finishing any unimplemented features necessary for parity with
+improving compatibility, performance, and finishing any unimplemented features necessary for parity with
the vanilla renderer.
### 💬 Join the Community
@@ -36,15 +64,16 @@ We have an [official Discord community](https://caffeinemc.net/discord) for all
## ✅ Hardware Compatibility
-We only provide support for graphics cards which have up-to-date drivers for OpenGL 4.6. Most graphics cards which have
-been released since year 2010 are supported, such as the...
+We only provide official support for graphics cards which have up-to-date drivers that are compatible with OpenGL 4.5
+or newer. Most graphics cards released in the past 12 years will meet these requirements, including the following:
- AMD Radeon HD 7000 Series (GCN 1) or newer
- NVIDIA GeForce 400 Series (Fermi) or newer
- Intel HD Graphics 500 Series (Skylake) or newer
-In some cases, older graphics cards may also work (so long as they have up-to-date drivers which have support for
-OpenGL 3.3), but they are not officially supported, and may not be compatible with future versions of Sodium.
+Nearly all graphics cards that are already compatible with Minecraft (which requires OpenGL 3.3) should also work
+with Sodium. But our team cannot ensure compatibility or provide support for older graphics cards, and they may
+not work with future versions of Sodium.
#### OpenGL Compatibility Layers
@@ -52,29 +81,23 @@ Devices which need to use OpenGL translation layers (such as GL4ES, ANGLE, etc)
not work with Sodium. These translation layers do not implement required functionality and they suffer from underlying
driver bugs which cannot be worked around.
-## 🛠️ Developer Guide
-
-### Building from sources
+## 🛠️ Building from sources
-Sodium uses a typical Gradle project structure and can be compiled by simply running the default `build` task. The build
-artifacts (typical mod binaries, and their sources) can be found in the `build/libs` directory.
+Sodium uses the [Gradle build tool](https://gradle.org/) and can be built with the `gradle build` command. The build
+artifacts (production binaries and their source bundles) can be found in the `build/mods` directory.
-#### Requirements
+The [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper) is provided for ease of use and will automatically download and install the
+appropriate version of Gradle for the project build. To use the Gradle wrapper, substitute `gradle` in build commands
+with `./gradlew.bat` (Windows) or `./gradlew` (macOS and Linux).
-We recommend using a package manager (such as [SDKMAN](https://sdkman.io/)) to manage toolchain dependencies and keep
-them up to date. For many Linux distributions, these dependencies will be standard packages in your software
-repositories.
+### Build Requirements
- OpenJDK 21
- - We recommend using the [Eclipse Temurin](https://adoptium.net/) distribution, as it's known to be high quality
- and to work without issues.
-- Gradle 8.6.x (optional)
- - The [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper) is provided
- in this repository can be used instead of installing a suitable version of Gradle yourself. However, if you are
- building many projects, you may prefer to install it yourself through a suitable package manager as to save disk
- space and to avoid many different Gradle daemons sitting around in memory.
+ - We recommend using the [Eclipse Temurin](https://adoptium.net/) distribution as it's regularly tested by our developers and known
+ to be of high quality.
+- Gradle 8.10.x
- Typically, newer versions of Gradle will work without issues, but the build script is only tested against the
- version specified by the wrapper script.
+ version used by the [wrapper script](/gradle/wrapper/gradle-wrapper.properties).
## 📜 License
diff --git a/build.gradle.kts b/build.gradle.kts
index d1a181b9da..e69de29bb2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,80 +0,0 @@
-plugins {
- id("java")
- id("fabric-loom") version ("1.7.3") apply (false)
-}
-
-val MINECRAFT_VERSION by extra { "1.21.1" }
-val NEOFORGE_VERSION by extra { "21.1.46" }
-val FABRIC_LOADER_VERSION by extra { "0.16.4" }
-val FABRIC_API_VERSION by extra { "0.103.0+1.21.1" }
-
-// This value can be set to null to disable Parchment.
-// TODO: Re-add Parchment
-val PARCHMENT_VERSION by extra { null }
-
-// https://semver.org/
-val MOD_VERSION by extra { "0.6.0-beta.2" }
-
-allprojects {
- apply(plugin = "java")
- apply(plugin = "maven-publish")
-}
-
-tasks.withType {
- options.encoding = "UTF-8"
-}
-
-tasks.jar {
- enabled = false
-}
-
-subprojects {
- apply(plugin = "maven-publish")
-
- java.toolchain.languageVersion = JavaLanguageVersion.of(21)
-
-
- fun createVersionString(): String {
- val builder = StringBuilder()
-
- val isReleaseBuild = project.hasProperty("build.release")
- val buildId = System.getenv("GITHUB_RUN_NUMBER")
-
- if (isReleaseBuild) {
- builder.append(MOD_VERSION)
- } else {
- builder.append(MOD_VERSION.substringBefore('-'))
- builder.append("-snapshot")
- }
-
- builder.append("+mc").append(MINECRAFT_VERSION)
-
- if (!isReleaseBuild) {
- if (buildId != null) {
- builder.append("-build.${buildId}")
- } else {
- builder.append("-local")
- }
- }
-
- return builder.toString()
- }
-
- tasks.processResources {
- filesMatching("META-INF/neoforge.mods.toml") {
- expand(mapOf("version" to createVersionString()))
- }
- }
-
- version = createVersionString()
- group = "net.caffeinemc.mods"
-
- tasks.withType {
- options.encoding = "UTF-8"
- options.release.set(21)
- }
-
- tasks.withType().configureEach {
- enabled = false
- }
-}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 0000000000..0a2aabdef4
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ `kotlin-dsl`
+}
+
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+}
diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt
new file mode 100644
index 0000000000..61c7fe177e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/BuildConfig.kt
@@ -0,0 +1,41 @@
+ import org.gradle.api.Project
+
+object BuildConfig {
+ val MINECRAFT_VERSION: String = "1.21.4"
+ val NEOFORGE_VERSION: String = "21.4.3-beta"
+ val FABRIC_LOADER_VERSION: String = "0.16.9"
+ val FABRIC_API_VERSION: String = "0.110.5+1.21.4"
+
+ // This value can be set to null to disable Parchment.
+ // TODO: Re-add Parchment
+ val PARCHMENT_VERSION: String? = null
+
+ // https://semver.org/
+ var MOD_VERSION: String = "0.6.3"
+
+ fun createVersionString(project: Project): String {
+ val builder = StringBuilder()
+
+ val isReleaseBuild = project.hasProperty("build.release")
+ val buildId = System.getenv("GITHUB_RUN_NUMBER")
+
+ if (isReleaseBuild) {
+ builder.append(MOD_VERSION)
+ } else {
+ builder.append(MOD_VERSION.substringBefore('-'))
+ builder.append("-snapshot")
+ }
+
+ builder.append("+mc").append(MINECRAFT_VERSION)
+
+ if (!isReleaseBuild) {
+ if (buildId != null) {
+ builder.append("-build.${buildId}")
+ } else {
+ builder.append("-local")
+ }
+ }
+
+ return builder.toString()
+ }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/multiloader-base.gradle.kts b/buildSrc/src/main/kotlin/multiloader-base.gradle.kts
new file mode 100644
index 0000000000..c23e78a4c6
--- /dev/null
+++ b/buildSrc/src/main/kotlin/multiloader-base.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+ id("java-library")
+ id("idea")
+}
+
+group = "net.caffeinemc"
+version = BuildConfig.createVersionString(project)
+
+java.toolchain.languageVersion = JavaLanguageVersion.of(21)
+
+tasks.withType {
+ options.encoding = "UTF-8"
+ options.release.set(21)
+}
+
+tasks.withType().configureEach {
+ enabled = false
+}
+
+repositories {
+ exclusiveContent {
+ forRepository {
+ maven {
+ name = "Modrinth"
+ url = uri("https://api.modrinth.com/maven")
+ }
+ }
+ filter {
+ includeGroup("maven.modrinth")
+ }
+ }
+
+ exclusiveContent {
+ forRepository {
+ maven {
+ name = "Parchment"
+ url = uri("https://maven.parchmentmc.org")
+ }
+ }
+ filter {
+ includeGroup("org.parchmentmc.data")
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/multiloader-platform.gradle.kts b/buildSrc/src/main/kotlin/multiloader-platform.gradle.kts
new file mode 100644
index 0000000000..006f9f3500
--- /dev/null
+++ b/buildSrc/src/main/kotlin/multiloader-platform.gradle.kts
@@ -0,0 +1,43 @@
+plugins {
+ id("multiloader-base")
+ id("maven-publish")
+}
+
+val configurationDesktopIntegrationJava: Configuration = configurations.create("commonDesktopIntegration") {
+ isCanBeResolved = true
+}
+
+dependencies {
+ configurationDesktopIntegrationJava(project(path = ":common", configuration = "commonDesktopJava"))
+}
+
+tasks {
+ processResources {
+ inputs.property("version", version)
+
+ filesMatching(listOf("fabric.mod.json", "META-INF/neoforge.mods.toml")) {
+ expand(mapOf("version" to version))
+ }
+ }
+
+ jar {
+ duplicatesStrategy = DuplicatesStrategy.FAIL
+ from(rootDir.resolve("LICENSE.md"))
+
+ // Entry-point for desktop integration when the file is executed directly
+ from(configurationDesktopIntegrationJava)
+ manifest.attributes["Main-Class"] = "net.caffeinemc.mods.sodium.desktop.LaunchWarn"
+ }
+}
+
+publishing {
+ publications {
+ create("maven") {
+ groupId = project.group as String
+ artifactId = project.name as String
+ version = version
+
+ from(components["java"])
+ }
+ }
+}
diff --git a/common/build.gradle.kts b/common/build.gradle.kts
index aba0458673..6595341f1e 100644
--- a/common/build.gradle.kts
+++ b/common/build.gradle.kts
@@ -1,48 +1,22 @@
plugins {
- id("java")
- id("idea")
- id("fabric-loom") version ("1.7.3")
-}
+ id("multiloader-base")
+ id("java-library")
-repositories {
- maven("https://maven.parchmentmc.org/")
+ id("fabric-loom") version ("1.8.9")
}
-val MINECRAFT_VERSION: String by rootProject.extra
-val PARCHMENT_VERSION: String? by rootProject.extra
-val FABRIC_LOADER_VERSION: String by rootProject.extra
-val FABRIC_API_VERSION: String by rootProject.extra
-
-dependencies {
- minecraft(group = "com.mojang", name = "minecraft", version = MINECRAFT_VERSION)
- mappings(loom.layered() {
- officialMojangMappings()
- if (PARCHMENT_VERSION != null) {
- parchment("org.parchmentmc.data:parchment-${MINECRAFT_VERSION}:${PARCHMENT_VERSION}@zip")
- }
- })
- compileOnly("io.github.llamalad7:mixinextras-common:0.3.5")
- annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.5")
- compileOnly("net.fabricmc:sponge-mixin:0.13.2+mixin.0.8.5")
-
- fun addDependentFabricModule(name: String) {
- val module = fabricApi.module(name, FABRIC_API_VERSION)
- modCompileOnly(module)
- }
-
- addDependentFabricModule("fabric-api-base")
- addDependentFabricModule("fabric-block-view-api-v2")
- addDependentFabricModule("fabric-renderer-api-v1")
- addDependentFabricModule("fabric-rendering-data-attachment-v1")
+base {
+ archivesName = "sodium-common"
+}
- modCompileOnly("net.fabricmc.fabric-api:fabric-renderer-api-v1:3.2.9+1172e897d7")
+val configurationPreLaunch = configurations.create("preLaunchDeps") {
+ isCanBeResolved = true
}
sourceSets {
val main = getByName("main")
val api = create("api")
val workarounds = create("workarounds")
- val desktop = create("desktop")
api.apply {
java {
@@ -52,13 +26,7 @@ sourceSets {
workarounds.apply {
java {
- compileClasspath += main.compileClasspath
- }
- }
-
- desktop.apply {
- java {
- srcDir("src/desktop/java")
+ compileClasspath += configurationPreLaunch
}
}
@@ -66,53 +34,94 @@ sourceSets {
java {
compileClasspath += api.output
compileClasspath += workarounds.output
- runtimeClasspath += api.output
}
}
-}
-loom {
- mixin {
- defaultRefmapName = "sodium.refmap.json"
- }
+ create("desktop")
+}
- accessWidenerPath = file("src/main/resources/sodium.accesswidener")
+dependencies {
+ minecraft(group = "com.mojang", name = "minecraft", version = BuildConfig.MINECRAFT_VERSION)
+ mappings(loom.layered {
+ officialMojangMappings()
- mods {
- val main by creating { // to match the default mod generated for Forge
- sourceSet("api")
- sourceSet("desktop")
- sourceSet("main")
+ if (BuildConfig.PARCHMENT_VERSION != null) {
+ parchment("org.parchmentmc.data:parchment-${BuildConfig.MINECRAFT_VERSION}:${BuildConfig.PARCHMENT_VERSION}@zip")
}
+ })
+
+ compileOnly("io.github.llamalad7:mixinextras-common:0.3.5")
+ annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.5")
+
+ compileOnly("net.fabricmc:sponge-mixin:0.13.2+mixin.0.8.5")
+ compileOnly("net.fabricmc:fabric-loader:${BuildConfig.FABRIC_LOADER_VERSION}")
+
+ fun addDependentFabricModule(name: String) {
+ modCompileOnly(fabricApi.module(name, BuildConfig.FABRIC_API_VERSION))
}
+
+ addDependentFabricModule("fabric-api-base")
+ addDependentFabricModule("fabric-block-view-api-v2")
+ addDependentFabricModule("fabric-renderer-api-v1")
+ addDependentFabricModule("fabric-rendering-data-attachment-v1")
+
+ // We need to be careful during pre-launch that we don't touch any Minecraft classes, since other mods
+ // will not yet have an opportunity to apply transformations.
+ configurationPreLaunch("org.lwjgl:lwjgl:3.3.3")
+ configurationPreLaunch("org.lwjgl:lwjgl-opengl:3.3.3")
+ configurationPreLaunch("net.java.dev.jna:jna:5.14.0")
+ configurationPreLaunch("net.java.dev.jna:jna-platform:5.14.0")
+ configurationPreLaunch("org.slf4j:slf4j-api:2.0.9")
+ configurationPreLaunch("org.jetbrains:annotations:25.0.0")
}
-tasks {
- getByName("compileDesktopJava") {
- sourceCompatibility = JavaVersion.VERSION_1_8.toString()
- targetCompatibility = JavaVersion.VERSION_1_8.toString()
+loom {
+ accessWidenerPath = file("src/main/resources/sodium-common.accesswidener")
+
+ mixin {
+ useLegacyMixinAp = false
}
+}
- jar {
- from(rootDir.resolve("LICENSE.md"))
+fun exportSourceSetJava(name: String, sourceSet: SourceSet) {
+ val configuration = configurations.create("${name}Java") {
+ isCanBeResolved = true
+ isCanBeConsumed = true
+ }
- val api = sourceSets.getByName("api")
- from(api.output.classesDirs)
- from(api.output.resourcesDir)
+ val compileTask = tasks.getByName(sourceSet.compileJavaTaskName)
+ artifacts.add(configuration.name, compileTask.destinationDirectory) {
+ builtBy(compileTask)
+ }
+}
- val workarounds = sourceSets.getByName("workarounds")
- from(workarounds.output.classesDirs)
- from(workarounds.output.resourcesDir)
+fun exportSourceSetResources(name: String, sourceSet: SourceSet) {
+ val configuration = configurations.create("${name}Resources") {
+ isCanBeResolved = true
+ isCanBeConsumed = true
+ }
- val desktop = sourceSets.getByName("desktop")
- from(desktop.output.classesDirs)
- from(desktop.output.resourcesDir)
+ val compileTask = tasks.getByName(sourceSet.processResourcesTaskName)
+ compileTask.apply {
+ exclude("**/README.txt")
+ exclude("/*.accesswidener")
+ }
- manifest.attributes["Main-Class"] = "net.caffeinemc.mods.sodium.desktop.LaunchWarn"
+ artifacts.add(configuration.name, compileTask.destinationDir) {
+ builtBy(compileTask)
}
}
-// This trick hides common tasks in the IDEA list.
-tasks.configureEach {
- group = null
-}
\ No newline at end of file
+// Exports the compiled output of the source set to the named configuration.
+fun exportSourceSet(name: String, sourceSet: SourceSet) {
+ exportSourceSetJava(name, sourceSet)
+ exportSourceSetResources(name, sourceSet)
+}
+
+exportSourceSet("commonMain", sourceSets["main"])
+exportSourceSet("commonApi", sourceSets["api"])
+exportSourceSet("commonEarlyLaunch", sourceSets["workarounds"])
+exportSourceSet("commonDesktop", sourceSets["desktop"])
+
+tasks.jar { enabled = false }
+tasks.remapJar { enabled = false }
\ No newline at end of file
diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java
index a7e86c52c9..6869755ae0 100644
--- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java
+++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java
@@ -1,5 +1,7 @@
package net.caffeinemc.mods.sodium.api.util;
+import java.nio.ByteOrder;
+
/**
* Provides some utilities for packing and unpacking color components from packed integer colors in ABGR format, which
* is used by OpenGL for color vectors.
@@ -9,11 +11,16 @@
* | Alpha | Blue | Green | Red |
*/
public class ColorABGR implements ColorU8 {
- private static final int RED_COMPONENT_OFFSET = 0;
+ private static final int RED_COMPONENT_OFFSET = 0;
private static final int GREEN_COMPONENT_OFFSET = 8;
- private static final int BLUE_COMPONENT_OFFSET = 16;
+ private static final int BLUE_COMPONENT_OFFSET = 16;
private static final int ALPHA_COMPONENT_OFFSET = 24;
+ private static final int RED_COMPONENT_MASK = COMPONENT_MASK << RED_COMPONENT_OFFSET;
+ private static final int GREEN_COMPONENT_MASK = COMPONENT_MASK << GREEN_COMPONENT_OFFSET;
+ private static final int BLUE_COMPONENT_MASK = COMPONENT_MASK << BLUE_COMPONENT_OFFSET;
+ private static final int ALPHA_COMPONENT_MASK = COMPONENT_MASK << ALPHA_COMPONENT_OFFSET;
+
/**
* Packs the specified color components into ABGR format. The alpha component is fully opaque.
* @param r The red component of the color
@@ -98,4 +105,51 @@ public static int unpackBlue(int color) {
public static int unpackAlpha(int color) {
return (color >> ALPHA_COMPONENT_OFFSET) & COMPONENT_MASK;
}
+
+ /**
+ * Multiplies the RGB components of the color with the provided factor. The alpha component is not modified.
+ *
+ * @param color The packed 32-bit ABGR color to be multiplied
+ * @param factor The darkening factor (in the range of 0..255) to multiply with
+ */
+ public static int mulRGB(int color, int factor) {
+ return (ColorMixer.mul(color, factor) & ~ALPHA_COMPONENT_MASK) | (color & ALPHA_COMPONENT_MASK);
+ }
+
+ /**
+ * See {@link #mulRGB(int, int)}. This function is identical, but it accepts a float in [0.0, 1.0] instead, which
+ * is then mapped to [0, 255].
+ *
+ * @param color The packed 32-bit ABGR color to be multiplied
+ * @param factor The darkening factor (in the range of 0.0..1.0) to multiply with
+ */
+ public static int mulRGB(int color, float factor) {
+ return mulRGB(color, ColorU8.normalizedFloatToByte(factor));
+ }
+
+ private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+
+ /**
+ * Shuffles the ordering of the ABGR color so that it can then be written out to memory in the platform's native
+ * byte ordering. This should be used when writing the packed color to memory (in native-order) as a 32-bit word.
+ */
+ public static int fromNativeByteOrder(int color) {
+ if (BIG_ENDIAN) {
+ return Integer.reverseBytes(color);
+ } else {
+ return color;
+ }
+ }
+
+ /**
+ * Shuffles the ordering of the ABGR color from the platform's native byte ordering. This should be used when reading
+ * the packed color from memory (in native-order) as a 32-bit word.
+ */
+ public static int toNativeByteOrder(int color) {
+ if (BIG_ENDIAN) {
+ return Integer.reverseBytes(color);
+ } else {
+ return color;
+ }
+ }
}
diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java
index f93880555e..2d7899111f 100644
--- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java
+++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java
@@ -15,6 +15,11 @@ public class ColorARGB implements ColorU8 {
private static final int GREEN_COMPONENT_OFFSET = 8;
private static final int BLUE_COMPONENT_OFFSET = 0;
+ private static final int RED_COMPONENT_MASK = COMPONENT_MASK << RED_COMPONENT_OFFSET;
+ private static final int GREEN_COMPONENT_MASK = COMPONENT_MASK << GREEN_COMPONENT_OFFSET;
+ private static final int BLUE_COMPONENT_MASK = COMPONENT_MASK << BLUE_COMPONENT_OFFSET;
+ private static final int ALPHA_COMPONENT_MASK = COMPONENT_MASK << ALPHA_COMPONENT_OFFSET;
+
/**
* Packs the specified color components into big-endian format for consumption by OpenGL.
* @param r The red component of the color
@@ -73,23 +78,40 @@ public static int unpackBlue(int color) {
}
/**
- * Re-packs the ARGB color into an ABGR color with the specified alpha component.
+ * Swizzles from ARGB format into ABGR format, replacing the alpha component with {@param alpha}.
*/
- public static int toABGR(int color, float alpha) {
- return Integer.reverseBytes(color << 8 | ColorU8.normalizedFloatToByte(alpha));
+ public static int toABGR(int color, int alpha) {
+ // shl(ARGB, 8) -> RGB0
+ // or(RGB0, 000A) -> RGBA
+ return Integer.reverseBytes(color << 8 | alpha);
}
/**
- * Re-packs the ARGB color into a aBGR color with the specified alpha component.
+ * Swizzles from ARGB format into ABGR format, replacing the alpha component with {@param alpha}. The alpha
+ * component is mapped from [0.0, 1.0] to [0, 255].
*/
- public static int toABGR(int color, int alpha) {
- return Integer.reverseBytes(color << 8 | alpha);
+ public static int toABGR(int color, float alpha) {
+ return toABGR(color, ColorU8.normalizedFloatToByte(alpha));
}
+ /**
+ * Swizzles from ARGB format into ABGR format.
+ */
public static int toABGR(int color) {
+ // rotateLeft(ARGB, 8) -> RGBA
+ // reverseBytes(RGBA) -> ABGR
return Integer.reverseBytes(Integer.rotateLeft(color, 8));
}
+ /**
+ * Swizzles from ABGR format into ARGB format.
+ */
+ public static int fromABGR(int color) {
+ // reverseBytes(ABGR) -> RGBA
+ // rotateRight(RGBA, 8) -> ARGB
+ return Integer.rotateRight(Integer.reverseBytes(color), 8);
+ }
+
/**
* Packs the specified color components into ARGB format.
* @param rgb The red/green/blue component of the color
@@ -98,4 +120,25 @@ public static int toABGR(int color) {
public static int withAlpha(int rgb, int alpha) {
return (alpha << ALPHA_COMPONENT_OFFSET) | (rgb & ~(COMPONENT_MASK << ALPHA_COMPONENT_OFFSET));
}
+
+ /**
+ * Multiplies the RGB components of the color with the provided factor. The alpha component is not modified.
+ *
+ * @param color The packed 32-bit ABGR color to be multiplied
+ * @param factor The darkening factor (in the range of 0..255) to multiply with
+ */
+ public static int mulRGB(int color, int factor) {
+ return (ColorMixer.mul(color, factor) & ~ALPHA_COMPONENT_MASK) | (color & ALPHA_COMPONENT_MASK);
+ }
+
+ /**
+ * See {@link #mulRGB(int, int)}. This function is identical, but it accepts a float in [0.0, 1.0] instead, which
+ * is then mapped to [0, 255].
+ *
+ * @param color The packed 32-bit ABGR color to be multiplied
+ * @param factor The darkening factor (in the range of 0.0..1.0) to multiply with
+ */
+ public static int mulRGB(int color, float factor) {
+ return mulRGB(color, ColorU8.normalizedFloatToByte(factor));
+ }
}
diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java
index a1934c8b44..220eae17ca 100644
--- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java
+++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java
@@ -1,77 +1,146 @@
package net.caffeinemc.mods.sodium.api.util;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * A collection of optimized color mixing functions which directly operate on packed color values. These functions are
+ * agnostic to the ordering of color channels, and the output value will always use the same channel ordering as
+ * the input values.
+ */
public class ColorMixer {
- private static final int CHANNEL_MASK = 0x00FF00FF;
+ /**
+ *
Linearly interpolate between the {@param start} and {@param end} points, represented as packed unsigned 8-bit
+ * values within a 32-bit integer. The result is computed as
(start * weight) + (end * (255 - weight))
+ * using fixed-point arithmetic and round-to-nearest behavior.
+ *
+ *
The results are undefined if {@param weight} is not within the interval [0, 255].
+ *
+ *
If {@param start} and {@param end} are the same value, the result of this function will always be that value,
+ * regardless of {@param weight}.
+ *
+ * @param start The value at the start of the range to interpolate
+ * @param end The value at the end of the range to interpolate
+ * @param weight The weight value used to interpolate between color values (in 0..255 range)
+ * @return The color that was interpolated between the start and end points
+ */
+ public static int mix(int start, int end, int weight) {
+ // De-interleave the 8-bit component lanes into high and low halves for each point.
+ // Multiply the start point by alpha, and the end point by 1-alpha, to produce Q8.8 fixed-point intermediates.
+ // Add the Q8.8 fixed-point intermediaries together to obtain the mixed values.
+ final long hi = ((start & 0x00FF00FFL) * weight) + ((end & 0x00FF00FFL) * (ColorU8.COMPONENT_MASK - weight));
+ final long lo = ((start & 0xFF00FF00L) * weight) + ((end & 0xFF00FF00L) * (ColorU8.COMPONENT_MASK - weight));
+
+ // Round the fixed-point values to the nearest integer, and interleave the high and low halves to
+ // produce the final packed result.
+ final long result =
+ (((hi + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) |
+ (((lo + 0xFF00FF00L) >>> 8) & 0xFF00FF00L);
+
+ return (int) result;
+ }
/**
- * Mixes a 32-bit color (with packed 8-bit components) into another using the given ratio. This is equivalent to
- * (color1 * ratio) + (color2 * (1.0 - ratio)) but uses bitwise trickery for maximum performance.
- *
- * The order of the channels within the packed color does not matter, and the output color will always
- * have the same ordering as the input colors.
+ *
This function is identical to {@link ColorMixer#mix(int, int, int)}, but {@param weight} is a normalized
+ * floating-point value within the interval of [0.0, 1.0].
+ *
+ *
The results are undefined if {@param weight} is not within the interval [0.0, 1.0].
*
- * @param aColor The color to mix towards
- * @param bColor The color to mix away from
- * @param ratio The percentage (in 0.0..1.0 range) to mix the first color into the second color
- * @return The mixed color in packed 32-bit format
+ * @param start The start of the range to interpolate
+ * @param end The end of the range to interpolate
+ * @param weight The weight value used to interpolate between color values (in 0.0..1.0 range)
+ * @return The color that was interpolated between the start and end points
*/
- public static int mix(int aColor, int bColor, float ratio) {
- int aRatio = (int) (256 * ratio); // int(ratio)
- int bRatio = 256 - aRatio; // int(1.0 - ratio)
-
- // Mask off and shift two components from each color into a packed vector of 16-bit components, where the
- // high 8 bits are all zeroes.
- int a1 = (aColor >> 0) & CHANNEL_MASK;
- int b1 = (bColor >> 0) & CHANNEL_MASK;
- int a2 = (aColor >> 8) & CHANNEL_MASK;
- int b2 = (bColor >> 8) & CHANNEL_MASK;
-
- // Multiply the packed 16-bit components against each mix factor, and add the components of each color
- // to produce the mixed result. This will never overflow since both 16-bit integers are in 0..255 range.
-
- // Then, shift the high 8 bits of each packed 16-bit component into the low 8 bits, and mask off the high bits of
- // each 16-bit component to produce a vector of packed 8-bit components, where every other component is empty.
- int c1 = (((a1 * aRatio) + (b1 * bRatio)) >> 8) & CHANNEL_MASK;
- int c2 = (((a2 * aRatio) + (b2 * bRatio)) >> 8) & CHANNEL_MASK;
-
- // Join the color components into the original order
- return ((c1 << 0) | (c2 << 8));
+ public static int mix(int start, int end, float weight) {
+ return mix(start, end, ColorU8.normalizedFloatToByte(weight));
}
/**
- * Multiplies the 32-bit colors (with packed 8-bit components) together.
- *
- * The order of the channels within the packed color does not matter, and the output color will always
- * have the same ordering as the input colors.
+ *
Performs bi-linear interpolation on a 2x2 matrix of color values to derive the point (x, y). This is more
+ * efficient than chaining {@link #mul(int, float)} calls.
*
- * @param a The first color to multiply
- * @param b The second color to multiply
- * @return The multiplied color in packed 32-bit format
+ *
The results are undefined if {@param x} and {@param y} are not within the interval [0.0, 1.0].
+ *
+ * @param m00 The packed color value for (0, 0)
+ * @param m01 The packed color value for (0, 1)
+ * @param m10 The packed color value for (1, 0)
+ * @param m11 The packed color value for (1, 1)
+ * @param x The amount to interpolate between x=0 and x=1
+ * @param y The amount to interpolate between y=0 and y=1
+ * @return The interpolated color value
*/
- public static int mul(int a, int b) {
- // Take each 8-bit component pair, multiply them together to create intermediate 16-bit integers,
- // and then shift the high half of each 16-bit integer into 8-bit integers.
- int c0 = (((a >> 0) & 0xFF) * ((b >> 0) & 0xFF)) >> 8;
- int c1 = (((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) >> 8;
- int c2 = (((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) >> 8;
- int c3 = (((a >> 24) & 0xFF) * ((b >> 24) & 0xFF)) >> 8;
-
- // Pack the components
- return (c0 << 0) | (c1 << 8) | (c2 << 16) | (c3 << 24);
+ @ApiStatus.Experimental
+ public static int mix2d(int m00, int m01, int m10, int m11, float x, float y) {
+ // The weights for each row and column in the matrix
+ int x1 = ColorU8.normalizedFloatToByte(x), x0 = 255 - x1;
+ int y1 = ColorU8.normalizedFloatToByte(y), y0 = 255 - y1;
+
+ // Blend across the X-axis
+ // (M00 * X0) + (M10 * X1)
+ long row0a = ((((m00 & 0x00FF00FFL) * x0) + (((m10 & 0x00FF00FFL) * x1)) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL;
+ long row0b = ((((m00 & 0xFF00FF00L) * x0) + (((m10 & 0xFF00FF00L) * x1)) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L;
+
+ // (M10 * X0) + (M11 * X1)
+ long row1a = ((((m01 & 0x00FF00FFL) * x0) + (((m11 & 0x00FF00FFL) * x1)) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL;
+ long row1b = ((((m01 & 0xFF00FF00L) * x0) + (((m11 & 0xFF00FF00L) * x1)) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L;
+
+ // Blend across the Y-axis
+ // (ROW0 * Y0) + (ROW1 * Y1)
+ long result = ((((row0a * y0) + ((row1a * y1)) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) |
+ ((((row0b * y0) + ((row1b * y1)) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L);
+
+ return (int) result;
}
/**
- * Multiplies the 32-bit colors with one component
+ *
Multiplies the packed 8-bit values component-wise to produce 16-bit intermediaries, and then round to the
+ * nearest 8-bit representation (similar to floating-point.)
+ *
+ * @param color0 The first color to multiply
+ * @param color1 The second color to multiply
+ * @return The product of the two colors
+ */
+ public static int mulComponentWise(int color0, int color1) {
+ int comp0 = ((((color0 >>> 0) & 0xFF) * ((color1 >>> 0) & 0xFF)) + 0xFF) >>> 8;
+ int comp1 = ((((color0 >>> 8) & 0xFF) * ((color1 >>> 8) & 0xFF)) + 0xFF) >>> 8;
+ int comp2 = ((((color0 >>> 16) & 0xFF) * ((color1 >>> 16) & 0xFF)) + 0xFF) >>> 8;
+ int comp3 = ((((color0 >>> 24) & 0xFF) * ((color1 >>> 24) & 0xFF)) + 0xFF) >>> 8;
+
+ return (comp0 << 0) | (comp1 << 8) | (comp2 << 16) | (comp3 << 24);
+ }
+
+ /**
+ *
Multiplies each 8-bit component against the factor to produce 16-bit intermediaries, and then round to the
+ * nearest 8-bit representation (similar to floating-point.)
+ *
+ *
The results are undefined if {@param factor} is not within the interval [0, 255].
+ *
+ * @param color The packed color values
+ * @param factor The multiplication factor (in 0..255 range)
+ * @return The result of the multiplication
+ */
+ public static int mul(int color, int factor) {
+ // De-interleave the 8-bit component lanes into high and low halves.
+ // Perform 8-bit multiplication to produce Q8.8 fixed-point intermediaries.
+ final long hi = (color & 0x00FF00FFL) * factor;
+ final long lo = (color & 0xFF00FF00L) * factor;
+
+ // Round the Q8.8 fixed-point values to the nearest integer, and interleave the high and low halves to
+ // produce the packed result.
+ final long result =
+ (((hi + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) |
+ (((lo + 0xFF00FF00L) >>> 8) & 0xFF00FF00L);
+
+ return (int) result;
+
+ }
+
+ /**
+ * See {@link #mul(int, int)}, which this function is identical to, except that it takes a floating point value in
+ * the interval of [0.0, 1.0] and maps it to [0, 255].
+ *
+ *
The results are undefined if {@param factor} is not within the interval [0.0, 1.0].
*/
- public static int mulSingle(int a, int b) {
- // Take each 8-bit component pair, multiply them together to create intermediate 16-bit integers,
- // and then shift the high half of each 16-bit integer into 8-bit integers.
- int c0 = (((a) & 0xFF) * b) >> 8;
- int c1 = (((a >> 8) & 0xFF) * b) >> 8;
- int c2 = (((a >> 16) & 0xFF) * b) >> 8;
- int c3 = (((a >> 24) & 0xFF) * b) >> 8;
-
- // Pack the components
- return (c0 << 0) | (c1 << 8) | (c2 << 16) | (c3 << 24);
+ public static int mul(int color, float factor) {
+ return mul(color, ColorU8.normalizedFloatToByte(factor));
}
}
diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java
index 3b467b27cf..002a2bd986 100644
--- a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java
+++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java
@@ -43,7 +43,7 @@ private static RuntimeException createUnsupportedVertexConsumerThrowable(VertexC
var name = clazz.getName();
return new IllegalArgumentException(("The class %s does not implement interface VertexBufferWriter, " +
- "which is required for compatibility with Sodium (see: https://github.com/CaffeineMC/sodium-fabric/issues/1620)").formatted(name));
+ "which is required for compatibility with Sodium (see: https://github.com/CaffeineMC/sodium/issues/1620)").formatted(name));
}
/**
diff --git a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java
index a1d55cb71f..577c762e52 100644
--- a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java
+++ b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java
@@ -7,7 +7,7 @@
import java.io.IOException;
public class LaunchWarn {
- private static final String HELP_URL = "https://github.com/CaffeineMC/sodium-fabric/wiki/Installation";
+ private static final String HELP_URL = "https://github.com/CaffeineMC/sodium/wiki/Installation";
private static final String RICH_MESSAGE =
"" +
diff --git a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/BrowseUrlHandler.java b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/BrowseUrlHandler.java
index 633021bf50..e7e3e989d9 100644
--- a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/BrowseUrlHandler.java
+++ b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/BrowseUrlHandler.java
@@ -8,15 +8,6 @@ public interface BrowseUrlHandler {
static BrowseUrlHandler createImplementation() {
// OpenJDK doesn't use xdg-open and fails to provide an implementation on most systems.
if (XDGImpl.isSupported()) {
- // Apparently xdg-open is just broken for some desktop environments, because *for some reason*
- // setting a default browser is complicated.
- if (KDEImpl.isSupported()) {
- return new KDEImpl();
- } else if (GNOMEImpl.isSupported()) {
- return new GNOMEImpl();
- }
-
- // If the user's desktop environment isn't KDE or GNOME, then we can only rely on xdg-open being present.
return new XDGImpl();
} else if (CrossPlatformImpl.isSupported()) {
return new CrossPlatformImpl();
diff --git a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/GNOMEImpl.java b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/GNOMEImpl.java
deleted file mode 100644
index b453efe2cf..0000000000
--- a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/GNOMEImpl.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package net.caffeinemc.mods.sodium.desktop.utils.browse;
-
-import java.io.IOException;
-import java.util.Objects;
-
-class GNOMEImpl implements BrowseUrlHandler {
- public static boolean isSupported() {
- return XDGImpl.isSupported() && Objects.equals(System.getenv("XDG_CURRENT_DESKTOP"), "GNOME");
- }
-
- @Override
- public void browseTo(String url) throws IOException {
- Runtime.getRuntime()
- .exec(new String[] { "gnome-open", url });
- }
-}
diff --git a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/KDEImpl.java b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/KDEImpl.java
deleted file mode 100644
index 61fb07d037..0000000000
--- a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/KDEImpl.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package net.caffeinemc.mods.sodium.desktop.utils.browse;
-
-import java.io.IOException;
-import java.util.Objects;
-
-class KDEImpl implements BrowseUrlHandler {
- public static boolean isSupported() {
- return XDGImpl.isSupported() && Objects.equals(System.getenv("XDG_CURRENT_DESKTOP"), "KDE");
- }
-
- @Override
- public void browseTo(String url) throws IOException {
- Runtime.getRuntime()
- .exec(new String[] { "kde-open", url });
- }
-}
diff --git a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/XDGImpl.java b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/XDGImpl.java
index 42adc65c1d..5c5884a552 100644
--- a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/XDGImpl.java
+++ b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/utils/browse/XDGImpl.java
@@ -1,7 +1,5 @@
package net.caffeinemc.mods.sodium.desktop.utils.browse;
-import net.caffeinemc.mods.sodium.desktop.utils.browse.BrowseUrlHandler;
-
import java.io.IOException;
import java.util.Locale;
@@ -15,7 +13,18 @@ public static boolean isSupported() {
@Override
public void browseTo(String url) throws IOException {
- Runtime.getRuntime()
+ var process = Runtime.getRuntime()
.exec(new String[] { "xdg-open", url });
+
+ try {
+ int result = process.waitFor();
+
+ if (result != 0 /* success */) {
+ throw new IOException("xdg-open exited with code: %d".formatted(result));
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java
index 56e0512812..6f9ad8117c 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java
@@ -47,7 +47,7 @@ public class ResourcePackScanner {
);
/**
- * #1569
+ * #1569
* Iterate through all active resource packs, and detect resource packs which contain files matching the blacklist.
* An error message is shown for resource packs which replace terrain core shaders.
* A warning is shown for resource packs which replace the default light.glsl and fog.glsl shaders.
@@ -115,14 +115,14 @@ private static void printCompatibilityReport(Collection sca
if (!entry.shaderPrograms.isEmpty()) {
emitProblem(builder,
"The resource pack replaces terrain shaders, which are not supported",
- "https://github.com/CaffeineMC/sodium-fabric/wiki/Resource-Packs",
+ "https://github.com/CaffeineMC/sodium/wiki/Resource-Packs",
entry.shaderPrograms);
}
if (!entry.shaderIncludes.isEmpty()) {
emitProblem(builder,
"The resource pack modifies shader include files, which are not fully supported",
- "https://github.com/CaffeineMC/sodium-fabric/wiki/Resource-Packs",
+ "https://github.com/CaffeineMC/sodium/wiki/Resource-Packs",
entry.shaderIncludes);
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/SodiumResourcePackMetadata.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/SodiumResourcePackMetadata.java
index 44898479b0..5a8a608588 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/SodiumResourcePackMetadata.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/SodiumResourcePackMetadata.java
@@ -17,5 +17,5 @@ public record SodiumResourcePackMetadata(List ignoredShaders) {
.apply(instance, SodiumResourcePackMetadata::new)
);
public static final MetadataSectionType SERIALIZER =
- MetadataSectionType.fromCodec("sodium", CODEC);
+ new MetadataSectionType<>("sodium", CODEC);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java
index b9ad8c36e8..c4434f2105 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java
@@ -12,7 +12,7 @@
import java.util.Properties;
/**
- * Documentation of these options...
+ * Documentation of these options...
*/
@SuppressWarnings("CanBeFinal")
public class MixinConfig {
@@ -230,7 +230,7 @@ private static void writeDefaultConfig(File file) throws IOException {
writer.write("# This is the configuration file for Sodium.\n");
writer.write("#\n");
writer.write("# You can find information on editing this file and all the available options here:\n");
- writer.write("# https://github.com/CaffeineMC/sodium-fabric/wiki/Configuration-File\n");
+ writer.write("# https://github.com/CaffeineMC/sodium/wiki/Configuration-File\n");
writer.write("#\n");
writer.write("# By default, this file will be empty except for this notice.\n");
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java
index f26cf032ce..f24feb54b7 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java
@@ -1,10 +1,13 @@
package net.caffeinemc.mods.sodium.client.gl.arena;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import net.caffeinemc.mods.sodium.client.gl.arena.staging.StagingBuffer;
import net.caffeinemc.mods.sodium.client.gl.buffer.GlBuffer;
import net.caffeinemc.mods.sodium.client.gl.buffer.GlBufferUsage;
import net.caffeinemc.mods.sodium.client.gl.buffer.GlMutableBuffer;
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
+import net.jpountz.xxhash.XXHash64;
+import net.jpountz.xxhash.XXHashFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -26,12 +29,17 @@ public class GlBufferArena {
private GlBufferSegment head;
- private int capacity;
- private int used;
+ private static final XXHash64 NATIVE_HASH = XXHashFactory.fastestInstance().hash64();
+ private static final XXHash64 JAVA_HASH = XXHashFactory.fastestJavaInstance().hash64();
+ private static final int NATIVE_HASH_BYTES_THRESHOLD = 512; // TODO: tune this?
+ private final Long2ReferenceOpenHashMap cache;
+
+ private long capacity;
+ private long used;
private final int stride;
- public GlBufferArena(CommandList commands, int initialCapacity, int stride, StagingBuffer stagingBuffer) {
+ public GlBufferArena(CommandList commands, int initialCapacity, int stride, StagingBuffer stagingBuffer, boolean enableCache) {
this.capacity = initialCapacity;
this.resizeIncrement = initialCapacity / 16;
@@ -44,16 +52,22 @@ public GlBufferArena(CommandList commands, int initialCapacity, int stride, Stag
commands.allocateStorage(this.arenaBuffer, this.capacity * stride, BUFFER_USAGE);
this.stagingBuffer = stagingBuffer;
+
+ if (enableCache) {
+ this.cache = new Long2ReferenceOpenHashMap<>();
+ } else {
+ this.cache = null;
+ }
}
- private void resize(CommandList commandList, int newCapacity) {
+ private void resize(CommandList commandList, long newCapacity) {
if (this.used > newCapacity) {
throw new UnsupportedOperationException("New capacity must be larger than used size");
}
this.checkAssertions();
- int tail = newCapacity - this.used;
+ long tail = newCapacity - this.used;
List usedSegments = this.getUsedSegments();
List pendingCopies = this.buildTransferList(usedSegments, tail);
@@ -66,7 +80,7 @@ private void resize(CommandList commandList, int newCapacity) {
if (usedSegments.isEmpty()) {
this.head.setNext(null);
} else {
- this.head.setNext(usedSegments.get(0));
+ this.head.setNext(usedSegments.getFirst());
this.head.getNext()
.setPrev(this.head);
}
@@ -74,23 +88,23 @@ private void resize(CommandList commandList, int newCapacity) {
this.checkAssertions();
}
- private List buildTransferList(List usedSegments, int base) {
+ private List buildTransferList(List usedSegments, long base) {
List pendingCopies = new ArrayList<>();
PendingBufferCopyCommand currentCopyCommand = null;
- int writeOffset = base;
+ long writeOffset = base;
for (int i = 0; i < usedSegments.size(); i++) {
GlBufferSegment s = usedSegments.get(i);
- if (currentCopyCommand == null || currentCopyCommand.readOffset + currentCopyCommand.length != s.getOffset()) {
+ if (currentCopyCommand == null || currentCopyCommand.getReadOffset() + currentCopyCommand.getLength() != s.getOffset()) {
if (currentCopyCommand != null) {
pendingCopies.add(currentCopyCommand);
}
currentCopyCommand = new PendingBufferCopyCommand(s.getOffset(), writeOffset, s.getLength());
} else {
- currentCopyCommand.length += s.getLength();
+ currentCopyCommand.setLength(currentCopyCommand.getLength() + s.getLength());
}
s.setOffset(writeOffset);
@@ -117,7 +131,11 @@ private List buildTransferList(List u
return pendingCopies;
}
- private void transferSegments(CommandList commandList, Collection list, int capacity) {
+ private void transferSegments(CommandList commandList, Collection list, long capacity) {
+ if (capacity >= (1L << 32)) {
+ throw new IllegalArgumentException("Maximum arena buffer size is 4 GiB");
+ }
+
GlMutableBuffer srcBufferObj = this.arenaBuffer;
GlMutableBuffer dstBufferObj = commandList.createMutableBuffer();
@@ -125,9 +143,9 @@ private void transferSegments(CommandList commandList, Collection getUsedSegments() {
return used;
}
- public int getDeviceUsedMemory() {
+ public long getDeviceUsedMemory() {
return this.used * this.stride;
}
- public int getDeviceAllocatedMemory() {
+ public long getDeviceAllocatedMemory() {
return this.capacity * this.stride;
}
@@ -222,6 +240,10 @@ public void free(GlBufferSegment entry) {
throw new IllegalStateException("Already freed");
}
+ if (entry.isHashed()) {
+ this.cache.remove(entry.getHash());
+ }
+
entry.setFree(true);
this.used -= entry.getLength();
@@ -261,7 +283,7 @@ public boolean upload(CommandList commandList, Stream stream) {
// A linked list is used as we'll be randomly removing elements and want O(1) performance
List queue = stream.collect(Collectors.toCollection(LinkedList::new));
- // Try to upload all of the data into free segments first
+ // Try to upload all the data into free segments first
this.tryUploads(commandList, queue);
// If we weren't able to upload some buffers, they will have been left behind in the queue
@@ -293,18 +315,46 @@ private void tryUploads(CommandList commandList, List queue) {
this.stagingBuffer.flush(commandList);
}
+ private long getBufferHash(ByteBuffer data) {
+ var seed = System.identityHashCode(this);
+ var length = data.remaining();
+ if (length < NATIVE_HASH_BYTES_THRESHOLD) {
+ return JAVA_HASH.hash(data, 0, length, seed);
+ } else {
+ return NATIVE_HASH.hash(data, 0, length, seed);
+ }
+ }
+
private boolean tryUpload(CommandList commandList, PendingUpload upload) {
- ByteBuffer data = upload.getDataBuffer()
- .getDirectBuffer();
+ ByteBuffer data = upload.getDataBuffer().getDirectBuffer();
int elementCount = data.remaining() / this.stride;
+ // return a buffer segment with the same content if there is one based on the hash of the incoming content
+ GlBufferSegment matchingSegment = null;
+ long hash = 0;
+ if (this.cache != null) {
+ hash = this.getBufferHash(data);
+ matchingSegment = this.cache.get(hash);
+ }
+ if (matchingSegment != null) {
+ upload.setResult(matchingSegment);
+ matchingSegment.addRef();
+ return true;
+ }
+
GlBufferSegment dst = this.alloc(elementCount);
if (dst == null) {
return false;
}
+ // if a new segment was needed (cache miss), set the calculated hash on the segment
+ if (this.cache != null) {
+ dst.setHash(hash);
+ this.cache.put(hash, dst);
+ }
+
// Copy the data into our staging buffer, then copy it into the arena's buffer
this.stagingBuffer.enqueueCopy(commandList, data, this.arenaBuffer, dst.getOffset() * this.stride);
@@ -313,11 +363,11 @@ private boolean tryUpload(CommandList commandList, PendingUpload upload) {
return true;
}
- public void ensureCapacity(CommandList commandList, int elementCount) {
+ public void ensureCapacity(CommandList commandList, long elementCount) {
// Re-sizing the arena results in a compaction, so any free space in the arena will be
// made into one contiguous segment, joined with the new segment of free space we're asking for
// We calculate the number of free elements in our arena and then subtract that from the total requested
- int elementsNeeded = elementCount - (this.capacity - this.used);
+ long elementsNeeded = elementCount - (this.capacity - this.used);
// Try to allocate some extra buffer space unless this is an unusually large allocation
this.resize(commandList, Math.max(this.capacity + this.resizeIncrement, this.capacity + elementsNeeded));
@@ -331,7 +381,7 @@ private void checkAssertions() {
private void checkAssertions0() {
GlBufferSegment seg = this.head;
- int used = 0;
+ long used = 0;
while (seg != null) {
if (seg.getOffset() < 0) {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferSegment.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferSegment.java
index 480727ee92..c32f739620 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferSegment.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferSegment.java
@@ -1,56 +1,77 @@
package net.caffeinemc.mods.sodium.client.gl.arena;
+import net.caffeinemc.mods.sodium.client.util.UInt32;
+
public class GlBufferSegment {
private final GlBufferArena arena;
private boolean free = false;
+ private int refCount = 1;
+ private long hash;
+ private boolean isHashed = false;
- private int offset;
- private int length;
+ private int offset; /* Uint32 */
+ private int length; /* Uint32 */
private GlBufferSegment next;
private GlBufferSegment prev;
- public GlBufferSegment(GlBufferArena arena, int offset, int length) {
+ public GlBufferSegment(GlBufferArena arena, long offset, long length) {
this.arena = arena;
- this.offset = offset;
- this.length = length;
+ this.offset = UInt32.downcast(offset);
+ this.length = UInt32.downcast(length);
}
- public void delete() {
- this.arena.free(this);
+ /* Uint32 */
+ protected long getEnd() {
+ return this.getOffset() + this.getLength();
}
- protected int getEnd() {
- return this.offset + this.length;
+ /* Uint32 */
+ public long getOffset() {
+ return UInt32.upcast(this.offset);
}
- public int getLength() {
- return this.length;
+ /* Uint32 */
+ public long getLength() {
+ return UInt32.upcast(this.length);
}
- protected void setLength(int len) {
- if (len <= 0) {
- throw new IllegalArgumentException("len <= 0");
- }
+ protected void setOffset(long offset /* Uint32 */) {
+ this.offset = UInt32.downcast(offset);
+ }
- this.length = len;
+ protected void setLength(long length /* Uint32 */) {
+ this.length = UInt32.downcast(length);
}
- public int getOffset() {
- return this.offset;
+ public void setHash(long hash) {
+ this.hash = hash;
+ this.isHashed = true;
}
- protected void setOffset(int offset) {
- if (offset < 0) {
- throw new IllegalArgumentException("start < 0");
- }
+ public long getHash() {
+ return this.hash;
+ }
+
+ public boolean isHashed() {
+ return this.isHashed;
+ }
- this.offset = offset;
+ public void addRef() {
+ if (this.isFree()) {
+ throw new IllegalStateException("Cannot add ref to free segment");
+ }
+ this.refCount++;
}
protected void setFree(boolean free) {
this.free = free;
+ if (this.free) {
+ this.refCount = 0;
+ } else {
+ this.refCount = Math.max(this.refCount, 1);
+ }
}
protected boolean isFree() {
@@ -73,6 +94,14 @@ protected void setPrev(GlBufferSegment prev) {
this.prev = prev;
}
+ public void delete() {
+ // only actually free if there's no more users
+ if (--this.refCount == 0) {
+ this.arena.free(this);
+ this.isHashed = false;
+ }
+ }
+
protected void mergeInto(GlBufferSegment entry) {
this.setLength(this.getLength() + entry.getLength());
this.setNext(entry.getNext());
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/PendingBufferCopyCommand.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/PendingBufferCopyCommand.java
index e7be6b80fb..8402d475c4 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/PendingBufferCopyCommand.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/PendingBufferCopyCommand.java
@@ -1,14 +1,35 @@
package net.caffeinemc.mods.sodium.client.gl.arena;
+import net.caffeinemc.mods.sodium.client.util.UInt32;
+
class PendingBufferCopyCommand {
- public final int readOffset;
- public final int writeOffset;
+ private final int readOffset; /* Uint32 */
+ private final int writeOffset; /* Uint32 */
+
+ private int length;
- public int length;
+ PendingBufferCopyCommand(long readOffset, long writeOffset, long length) {
+ this.readOffset = UInt32.downcast(readOffset);
+ this.writeOffset = UInt32.downcast(writeOffset);
+ this.length = UInt32.downcast(length);
+ }
+
+ /* Uint32 */
+ public long getReadOffset() {
+ return UInt32.upcast(this.readOffset);
+ }
+
+ /* Uint32 */
+ public long getWriteOffset() {
+ return UInt32.upcast(this.writeOffset);
+ }
+
+ /* Uint32 */
+ public long getLength() {
+ return UInt32.upcast(this.length);
+ }
- PendingBufferCopyCommand(int readOffset, int writeOffset, int length) {
- this.readOffset = readOffset;
- this.writeOffset = writeOffset;
- this.length = length;
+ public void setLength(long length /* Uint32 */) {
+ this.length = UInt32.downcast(length);
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java
deleted file mode 100644
index 12b879ba99..0000000000
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package net.caffeinemc.mods.sodium.client.gl.buffer;
-
-import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexFormat;
-import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
-
-/**
- * Helper type for tagging the vertex format alongside the raw buffer data.
- */
-public record IndexedVertexData(GlVertexFormat vertexFormat,
- NativeBuffer vertexBuffer,
- NativeBuffer indexBuffer) {
- public void delete() {
- this.vertexBuffer.free();
- this.indexBuffer.free();
- }
-}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java
index ceda21721a..0eb9a381fb 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java
@@ -53,6 +53,7 @@ public boolean isEmpty() {
public int getIndexBufferSize() {
int elements = 0;
+ // since there's command combining, all facings might be rendered at the same time with a single command which requires a bigger index buffer
for (var index = 0; index < this.size; index++) {
elements = Math.max(elements, MemoryUtil.memGetInt(this.pElementCount + ((long) index * Integer.BYTES)));
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java
index c968f69bdb..b8668e7f76 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java
@@ -15,4 +15,8 @@ public void set(float[] value) {
GL30C.glUniform4fv(this.index, value);
}
+
+ public void set(float x, float y, float z, float w) {
+ GL30C.glUniform4f(this.index, x, y, z, w);
+ }
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java
index b512dddc4e..6a43897da0 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java
@@ -2,37 +2,41 @@
import com.google.common.collect.ImmutableList;
import com.mojang.blaze3d.pipeline.RenderTarget;
+import com.mojang.blaze3d.platform.Monitor;
+import com.mojang.blaze3d.platform.VideoMode;
import com.mojang.blaze3d.platform.Window;
+import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils;
import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer;
import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice;
import net.caffeinemc.mods.sodium.client.gui.options.*;
import net.caffeinemc.mods.sodium.client.gui.options.binding.compat.VanillaBooleanOptionBinding;
-import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatter;
-import net.caffeinemc.mods.sodium.client.gui.options.control.CyclingControl;
-import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl;
-import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl;
+import net.caffeinemc.mods.sodium.client.gui.options.control.*;
import net.caffeinemc.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
import net.caffeinemc.mods.sodium.client.gui.options.storage.SodiumOptionsStorage;
import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds;
import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation;
import net.minecraft.client.AttackIndicatorStatus;
+import net.minecraft.client.InactivityFpsLimit;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.GraphicsStatus;
import net.minecraft.client.Minecraft;
-import net.minecraft.client.ParticleStatus;
import net.minecraft.network.chat.Component;
+import net.minecraft.server.level.ParticleStatus;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
// TODO: Rename in Sodium 0.6
public class SodiumGameOptionPages {
private static final SodiumOptionsStorage sodiumOpts = new SodiumOptionsStorage();
private static final MinecraftOptionsStorage vanillaOpts = new MinecraftOptionsStorage();
+ private static final Window window = Minecraft.getInstance().getWindow();
public static OptionPage general() {
+ Monitor monitor = window.findBestMonitor();
List groups = new ArrayList<>();
groups.add(OptionGroup.createBuilder()
@@ -90,6 +94,26 @@ public static OptionPage general() {
}
}, (opts) -> opts.fullscreen().get())
.build())
+ .add(OptionImpl.createBuilder(int.class, vanillaOpts)
+ .setName(Component.translatable("options.fullscreen.resolution"))
+ .setTooltip(Component.translatable("sodium.options.fullscreen_resolution.tooltip"))
+ .setControl(option -> new SliderControl(option, 0, null != monitor? monitor.getModeCount(): 0, 1, ControlValueFormatter.resolution()))
+ .setBinding((options, value) -> {
+ if (null != monitor) {
+ window.setPreferredFullscreenVideoMode(0 == value? Optional.empty(): Optional.of(monitor.getMode(value - 1)));
+ }
+ }, options -> {
+ if (null == monitor) {
+ return 0;
+ }
+ else {
+ Optional optional = window.getPreferredFullscreenVideoMode();
+ return optional.map((videoMode) -> monitor.getVideoModeIndex(videoMode) + 1).orElse(0);
+ }
+ })
+ .setEnabled(() -> OsUtils.getOs() == OsUtils.OperatingSystem.WIN && Minecraft.getInstance().getWindow().findBestMonitor() != null)
+ .setFlags(OptionFlag.REQUIRES_VIDEOMODE_RELOAD)
+ .build())
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(Component.translatable("options.vsync"))
.setTooltip(Component.translatable("sodium.options.v_sync.tooltip"))
@@ -103,7 +127,7 @@ public static OptionPage general() {
.setControl(option -> new SliderControl(option, 10, 260, 10, ControlValueFormatter.fpsLimit()))
.setBinding((opts, value) -> {
opts.framerateLimit().set(value);
- Minecraft.getInstance().getWindow().setFramerateLimit(value);
+ Minecraft.getInstance().getFramerateLimitTracker().setFramerateLimit(value);
}, opts -> opts.framerateLimit().get())
.build())
.build());
@@ -159,7 +183,7 @@ public static OptionPage quality() {
if (Minecraft.useShaderTransparency()) {
RenderTarget framebuffer = Minecraft.getInstance().levelRenderer.getCloudsTarget();
if (framebuffer != null) {
- framebuffer.clear(Minecraft.ON_OSX);
+ framebuffer.clear();
}
}
}, opts -> opts.cloudStatus().get())
@@ -311,6 +335,12 @@ public static OptionPage performance() {
.setEnabled(SodiumGameOptionPages::supportsNoErrorContext)
.setFlags(OptionFlag.REQUIRES_GAME_RESTART)
.build())
+ .add(OptionImpl.createBuilder(InactivityFpsLimit.class, vanillaOpts)
+ .setName(Component.translatable("options.inactivityFpsLimit"))
+ .setTooltip(v -> Component.translatable(v.getId() == 0 ? "options.inactivityFpsLimit.minimized.tooltip" : "options.inactivityFpsLimit.afk.tooltip"))
+ .setControl(option -> new CyclingControl<>(option, InactivityFpsLimit.class, new Component[] { Component.translatable("options.inactivityFpsLimit.minimized"), Component.translatable("options.inactivityFpsLimit.afk") }))
+ .setBinding((opts, value) -> opts.inactivityFpsLimit().set(value), opts -> opts.inactivityFpsLimit().get())
+ .build())
.build());
if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java
index 876bdb44bd..aba63b7b0b 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java
@@ -295,18 +295,21 @@ private void renderOptionTooltip(GuiGraphics graphics, ControlElement> element
int textPadding = 3;
int boxPadding = 3;
- int boxWidth = 200;
-
int boxY = dim.y();
int boxX = dim.getLimitX() + boxPadding;
+ int boxWidth = Math.min(200, this.width - boxX - boxPadding);
+
Option> option = element.getOption();
- List tooltip = new ArrayList<>(this.font.split(option.getTooltip(), boxWidth - (textPadding * 2)));
+ var splitWidth = boxWidth - (textPadding * 2);
+ List tooltip = new ArrayList<>(this.font.split(option.getTooltip(),splitWidth));
OptionImpact impact = option.getImpact();
if (impact != null) {
- tooltip.add(Language.getInstance().getVisualOrder(Component.translatable("sodium.options.performance_impact_string", impact.getLocalizedName()).withStyle(ChatFormatting.GRAY)));
+ var impactText = Component.translatable("sodium.options.performance_impact_string",
+ impact.getLocalizedName());
+ tooltip.addAll(this.font.split(impactText.withStyle(ChatFormatting.GRAY), splitWidth));
}
int boxHeight = (tooltip.size() * 12) + boxPadding;
@@ -355,6 +358,10 @@ private void applyChanges() {
client.delayTextureReload();
}
+ if (flags.contains(OptionFlag.REQUIRES_VIDEOMODE_RELOAD)) {
+ client.getWindow().changeFullscreenVideoMode();
+ }
+
if (flags.contains(OptionFlag.REQUIRES_GAME_RESTART)) {
Console.instance().logMessage(MessageLevel.WARN,
"sodium.console.game_restart", true, 10.0);
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java
index 8ea3e5b0bf..17568af5a5 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java
@@ -4,5 +4,6 @@ public enum OptionFlag {
REQUIRES_RENDERER_RELOAD,
REQUIRES_RENDERER_UPDATE,
REQUIRES_ASSET_RELOAD,
+ REQUIRES_VIDEOMODE_RELOAD,
REQUIRES_GAME_RESTART
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java
index 893bea4363..ee5d3b2892 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java
@@ -23,7 +23,7 @@ public class OptionImpl implements Option {
private final EnumSet flags;
private final Component name;
- private final Component tooltip;
+ private final Function tooltip;
private final OptionImpact impact;
@@ -34,7 +34,7 @@ public class OptionImpl implements Option {
private OptionImpl(OptionStorage storage,
Component name,
- Component tooltip,
+ Function tooltip,
OptionBinding binding,
Function, Control> control,
EnumSet flags,
@@ -59,7 +59,7 @@ public Component getName() {
@Override
public Component getTooltip() {
- return this.tooltip;
+ return this.tooltip.apply(this.modifiedValue);
}
@Override
@@ -121,7 +121,7 @@ public static OptionImpl.Builder createBuilder(@SuppressWarnings("u
public static class Builder {
private final OptionStorage storage;
private Component name;
- private Component tooltip;
+ private Function tooltip;
private OptionBinding binding;
private Function, Control> control;
private OptionImpact impact;
@@ -143,6 +143,14 @@ public Builder setName(Component name) {
public Builder setTooltip(Component tooltip) {
Validate.notNull(tooltip, "Argument must not be null");
+ this.tooltip = t -> tooltip;
+
+ return this;
+ }
+
+ public Builder setTooltip(Function tooltip) {
+ Validate.notNull(tooltip, "Argument must not be null");
+
this.tooltip = tooltip;
return this;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java
index 3df9998470..fbdb182130 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java
@@ -8,6 +8,7 @@
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.navigation.FocusNavigationEvent;
import net.minecraft.client.gui.navigation.ScreenRectangle;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ControlElement extends AbstractWidget {
@@ -20,18 +21,28 @@ public ControlElement(Option option, Dim2i dim) {
this.dim = dim;
}
+ public int getContentWidth() {
+ return this.option.getControl().getMaxWidth();
+ }
+
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
String name = this.option.getName().getString();
- String label;
- if ((this.hovered || this.isFocused()) && this.font.width(name) > (this.dim.width() - this.option.getControl().getMaxWidth())) {
- name = name.substring(0, Math.min(name.length(), 10)) + "...";
+ // add the star suffix before truncation to prevent it from overlapping with the label text
+ if (this.option.isAvailable() && this.option.hasChanged()) {
+ name = name + " *";
+ }
+
+ // on focus or hover truncate the label to never overlap with the control's content
+ if (this.hovered || this.isFocused()) {
+ name = truncateLabelToFit(name);
}
+ String label;
if (this.option.isAvailable()) {
if (this.option.hasChanged()) {
- label = ChatFormatting.ITALIC + name + " *";
+ label = ChatFormatting.ITALIC + name;
} else {
label = ChatFormatting.WHITE + name;
}
@@ -49,6 +60,33 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
}
}
+ private @NotNull String truncateLabelToFit(String name) {
+ var suffix = "...";
+ var suffixWidth = this.font.width(suffix);
+ var nameFontWidth = this.font.width(name);
+ var targetWidth = this.dim.width() - this.getContentWidth() - 20;
+ if (nameFontWidth > targetWidth) {
+ targetWidth -= suffixWidth;
+ int maxLabelChars = name.length() - 3;
+ int minLabelChars = 1;
+
+ // binary search on how many chars fit
+ while (maxLabelChars - minLabelChars > 1) {
+ var mid = (maxLabelChars + minLabelChars) / 2;
+ var midName = name.substring(0, mid);
+ var midWidth = this.font.width(midName);
+ if (midWidth > targetWidth) {
+ maxLabelChars = mid;
+ } else {
+ minLabelChars = mid;
+ }
+ }
+
+ name = name.substring(0, minLabelChars).trim() + suffix;
+ }
+ return name;
+ }
+
public Option getOption() {
return this.option;
}
@@ -68,4 +106,9 @@ public Dim2i getDimensions() {
public ScreenRectangle getRectangle() {
return new ScreenRectangle(this.dim.x(), this.dim.y(), this.dim.width(), this.dim.height());
}
+
+ @Override
+ public boolean isMouseOver(double x, double y) {
+ return this.dim.containsCursor(x, y);
+ }
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java
index e72a682b1b..c7d8e30507 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java
@@ -1,5 +1,8 @@
package net.caffeinemc.mods.sodium.client.gui.options.control;
+import com.mojang.blaze3d.platform.Monitor;
+import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils;
+import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
public interface ControlValueFormatter {
@@ -7,6 +10,19 @@ static ControlValueFormatter guiScale() {
return (v) -> (v == 0) ? Component.translatable("options.guiScale.auto") : Component.literal(v + "x");
}
+ static ControlValueFormatter resolution() {
+ return (v) -> {
+ Monitor monitor = Minecraft.getInstance().getWindow().findBestMonitor();
+
+ if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN || monitor == null) {
+ return Component.translatable("options.fullscreen.unavailable");
+ } else if (0 == v) {
+ return Component.translatable("options.fullscreen.current");
+ } else {
+ return Component.literal(monitor.getMode(v - 1).toString().replace(" (24bit)",""));
+ }
+ };
+ }
static ControlValueFormatter fpsLimit() {
return (v) -> (v == 260) ? Component.translatable("options.framerateLimit.max") : Component.translatable("options.framerate", v);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java
index 402191ee52..99c379b493 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java
@@ -3,9 +3,10 @@
import com.mojang.blaze3d.platform.InputConstants;
import net.caffeinemc.mods.sodium.client.gui.options.Option;
import net.caffeinemc.mods.sodium.client.util.Dim2i;
+import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.Rect2i;
-import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.Style;
import net.minecraft.util.Mth;
import org.apache.commons.lang3.Validate;
@@ -48,6 +49,7 @@ private static class Button extends ControlElement {
private static final int THUMB_WIDTH = 2, TRACK_HEIGHT = 1;
private final Rect2i sliderBounds;
+ private int contentWidth;
private final ControlValueFormatter formatter;
private final int min;
@@ -75,48 +77,52 @@ public Button(Option option, Dim2i dim, int min, int max, int interval,
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
- super.render(graphics, mouseX, mouseY, delta);
-
- if (this.option.isAvailable() && (this.hovered || this.isFocused())) {
- this.renderSlider(graphics);
- } else {
- this.renderStandaloneValue(graphics);
- }
- }
-
- private void renderStandaloneValue(GuiGraphics graphics) {
int sliderX = this.sliderBounds.getX();
int sliderY = this.sliderBounds.getY();
int sliderWidth = this.sliderBounds.getWidth();
int sliderHeight = this.sliderBounds.getHeight();
- Component label = this.formatter.format(this.option.getValue());
- int labelWidth = this.font.width(label);
+ var label = this.formatter.format(this.option.getValue())
+ .copy();
- this.drawString(graphics, label, sliderX + sliderWidth - labelWidth, sliderY + (sliderHeight / 2) - 4, 0xFFFFFFFF);
- }
+ if (!this.option.isAvailable()) {
+ label.setStyle(Style.EMPTY
+ .withColor(ChatFormatting.GRAY)
+ .withItalic(true));
+ }
- private void renderSlider(GuiGraphics graphics) {
- int sliderX = this.sliderBounds.getX();
- int sliderY = this.sliderBounds.getY();
- int sliderWidth = this.sliderBounds.getWidth();
- int sliderHeight = this.sliderBounds.getHeight();
+ int labelWidth = this.font.width(label);
- this.thumbPosition = this.getThumbPositionForValue(this.option.getValue());
+ boolean drawSlider = this.option.isAvailable() && (this.hovered || this.isFocused());
+ if (drawSlider) {
+ this.contentWidth = sliderWidth + labelWidth;
+ } else {
+ this.contentWidth = labelWidth;
+ }
- double thumbOffset = Mth.clamp((double) (this.getIntValue() - this.min) / this.range * sliderWidth, 0, sliderWidth);
+ // render the label first and then the slider to prevent the highlight rect from darkening the slider
+ super.render(graphics, mouseX, mouseY, delta);
- int thumbX = (int) (sliderX + thumbOffset - THUMB_WIDTH);
- int trackY = (int) (sliderY + (sliderHeight / 2f) - ((double) TRACK_HEIGHT / 2));
+ if (drawSlider) {
+ this.thumbPosition = this.getThumbPositionForValue(this.option.getValue());
- this.drawRect(graphics, thumbX, sliderY, thumbX + (THUMB_WIDTH * 2), sliderY + sliderHeight, 0xFFFFFFFF);
- this.drawRect(graphics, sliderX, trackY, sliderX + sliderWidth, trackY + TRACK_HEIGHT, 0xFFFFFFFF);
+ double thumbOffset = Mth.clamp((double) (this.getIntValue() - this.min) / this.range * sliderWidth, 0, sliderWidth);
- Component label = this.formatter.format(this.getIntValue());
+ int thumbX = (int) (sliderX + thumbOffset - THUMB_WIDTH);
+ int trackY = (int) (sliderY + (sliderHeight / 2f) - ((double) TRACK_HEIGHT / 2));
- int labelWidth = this.font.width(label);
+ this.drawRect(graphics, thumbX, sliderY, thumbX + (THUMB_WIDTH * 2), sliderY + sliderHeight, 0xFFFFFFFF);
+ this.drawRect(graphics, sliderX, trackY, sliderX + sliderWidth, trackY + TRACK_HEIGHT, 0xFFFFFFFF);
- this.drawString(graphics, label, sliderX - labelWidth - 6, sliderY + (sliderHeight / 2) - 4, 0xFFFFFFFF);
+ this.drawString(graphics, label, sliderX - labelWidth - 6, sliderY + (sliderHeight / 2) - 4, 0xFFFFFFFF);
+ } else {
+ this.drawString(graphics, label, sliderX + sliderWidth - labelWidth, sliderY + (sliderHeight / 2) - 4, 0xFFFFFFFF);
+ }
+ }
+
+ @Override
+ public int getContentWidth() {
+ return this.contentWidth;
}
public int getIntValue() {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/AbstractWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/AbstractWidget.java
index cf494b3e2c..30a3d5d3b2 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/AbstractWidget.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/AbstractWidget.java
@@ -102,4 +102,7 @@ protected void drawBorder(GuiGraphics graphics, int x1, int y1, int x2, int y2,
graphics.fill(x1, y1, x1 + 1, y2, color);
graphics.fill(x2 - 1, y1, x2, y2, color);
}
+
+ @Override
+ public abstract boolean isMouseOver(double x, double y);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java
index 7d753555d1..4bdd04e73a 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java
@@ -121,6 +121,11 @@ public Component getLabel() {
return super.nextFocusPath(event);
}
+ @Override
+ public boolean isMouseOver(double x, double y) {
+ return this.dim.containsCursor(x, y);
+ }
+
@Override
public ScreenRectangle getRectangle() {
return new ScreenRectangle(this.dim.x(), this.dim.y(), this.dim.width(), this.dim.height());
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java
index 848896141b..c80d52022c 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java
@@ -7,7 +7,7 @@
import net.minecraft.client.renderer.BiomeColors;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.material.FluidState;
+
import java.util.Arrays;
public class DefaultColorProviders {
@@ -50,7 +50,7 @@ private VanillaAdapter(BlockColor color) {
@Override
public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, BlockState state, ModelQuadView quad, int[] output) {
- Arrays.fill(output, 0xFF000000 | this.color.getColor(state, slice, pos, quad.getColorIndex()));
+ Arrays.fill(output, 0xFF000000 | this.color.getColor(state, slice, pos, quad.getTintIndex()));
}
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/interop/ItemColorsExtension.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/interop/ItemColorsExtension.java
deleted file mode 100644
index cee52b119f..0000000000
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/interop/ItemColorsExtension.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package net.caffeinemc.mods.sodium.client.model.color.interop;
-
-import net.minecraft.client.color.item.ItemColor;
-import net.minecraft.world.item.ItemStack;
-
-public interface ItemColorsExtension {
- ItemColor sodium$getColorProvider(ItemStack stack);
-}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java
index 0ded5bc8c9..1b04155e03 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java
@@ -65,8 +65,8 @@ protected int compute(int x, int y, int z) {
BlockState state = level.getBlockState(pos);
boolean em = state.emissiveRendering(level, pos);
- boolean op = state.isViewBlocking(level, pos) && state.getLightBlock(level, pos) != 0;
- boolean fo = state.isSolidRender(level, pos);
+ boolean op = state.isViewBlocking(level, pos) && state.getLightBlock() != 0;
+ boolean fo = state.isSolidRender();
boolean fc = state.isCollisionShapeFullBlock(level, pos);
int lu = PlatformBlockAccess.getInstance().getLightEmission(state, level, pos);
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/BakedQuadView.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/BakedQuadView.java
index 8acff21468..27673a7f99 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/BakedQuadView.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/BakedQuadView.java
@@ -8,4 +8,6 @@ public interface BakedQuadView extends ModelQuadView {
int getFaceNormal();
boolean hasShade();
+
+ boolean hasAO();
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java
index d5f650313d..54a01fd3bf 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java
@@ -15,7 +15,7 @@ public class ModelQuad implements ModelQuadViewMutable {
private TextureAtlasSprite sprite;
private Direction direction;
- private int colorIdx;
+ private int tintIdx;
private int faceNormal;
@Override
@@ -74,8 +74,8 @@ public void setSprite(TextureAtlasSprite sprite) {
}
@Override
- public void setColorIndex(int index) {
- this.colorIdx = index;
+ public void setTintIndex(int index) {
+ this.tintIdx = index;
}
@Override
@@ -84,8 +84,8 @@ public void setLightFace(Direction direction) {
}
@Override
- public int getColorIndex() {
- return this.colorIdx;
+ public int getTintIndex() {
+ return this.tintIdx;
}
@Override
@@ -147,4 +147,9 @@ public TextureAtlasSprite getSprite() {
public Direction getLightFace() {
return this.direction;
}
+
+ @Override
+ public int getMaxLightQuad(int idx) {
+ return getLight(idx);
+ }
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java
index 08369fb0af..f04685e987 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java
@@ -60,9 +60,9 @@ public interface ModelQuadView {
int getFlags();
/**
- * @return The color index of this quad.
+ * @return The tint index of this quad.
*/
- int getColorIndex();
+ int getTintIndex();
/**
* @return The sprite texture used by this quad, or null if none is attached
@@ -75,7 +75,7 @@ public interface ModelQuadView {
Direction getLightFace();
default boolean hasColor() {
- return this.getColorIndex() != -1;
+ return this.getTintIndex() != -1;
}
default int calculateNormal() {
@@ -127,4 +127,11 @@ default int getAccurateNormal(int i) {
return normal == 0 ? getFaceNormal() : normal;
}
+
+ /**
+ * Gets the maximum light value for this vertex.
+ * @param idx The vertex index.
+ * @return Lightmap value.
+ */
+ int getMaxLightQuad(int idx);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadViewMutable.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadViewMutable.java
index 30753db6ca..02063e2cb3 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadViewMutable.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadViewMutable.java
@@ -69,7 +69,7 @@ public interface ModelQuadViewMutable extends ModelQuadView {
void setLightFace(Direction direction);
/**
- * Sets the color index used by this quad
+ * Sets the tint index used by this quad
*/
- void setColorIndex(int index);
+ void setTintIndex(int index);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java
index b1d53d164a..24014df9b9 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java
@@ -3,7 +3,6 @@
import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView;
import net.caffeinemc.mods.sodium.client.model.color.ColorProvider;
import net.caffeinemc.mods.sodium.client.world.LevelSlice;
-import net.caffeinemc.mods.sodium.api.util.ColorARGB;
import net.caffeinemc.mods.sodium.api.util.ColorMixer;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
@@ -17,56 +16,40 @@ public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos s
}
private int getVertexColor(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, ModelQuadView quad, T state, int vertexIndex) {
- // Offset the position by -0.5f to align smooth blending with flat blending.
- final float posX = quad.getX(vertexIndex) - 0.5f;
- final float posY = quad.getY(vertexIndex) - 0.5f;
- final float posZ = quad.getZ(vertexIndex) - 0.5f;
-
- // Floor the positions here to always get the largest integer below the input
- // as negative values by default round toward zero when casting to an integer.
- // Which would cause negative ratios to be calculated in the interpolation later on.
- final int posIntX = Mth.floor(posX);
- final int posIntY = Mth.floor(posY);
- final int posIntZ = Mth.floor(posZ);
-
- // Integer component of position vector
- final int blockIntX = pos.getX() + posIntX;
- final int blockIntY = pos.getY() + posIntY;
- final int blockIntZ = pos.getZ() + posIntZ;
-
- // Retrieve the color values for each neighboring block
- final int c00 = this.getColor(slice, state, scratchPos.set(blockIntX + 0, blockIntY, blockIntZ + 0));
- final int c01 = this.getColor(slice, state, scratchPos.set(blockIntX + 0, blockIntY, blockIntZ + 1));
- final int c10 = this.getColor(slice, state, scratchPos.set(blockIntX + 1, blockIntY, blockIntZ + 0));
- final int c11 = this.getColor(slice, state, scratchPos.set(blockIntX + 1, blockIntY, blockIntZ + 1));
-
- // Linear interpolation across the Z-axis
- int z0;
-
- if (c00 != c01) {
- z0 = ColorMixer.mix(c00, c01, posZ - posIntZ);
- } else {
- z0 = c00;
- }
-
- int z1;
-
- if (c10 != c11) {
- z1 = ColorMixer.mix(c10, c11, posZ - posIntZ);
- } else {
- z1 = c10;
- }
-
- // Linear interpolation across the X-axis
- int x0;
-
- if (z0 != z1) {
- x0 = ColorMixer.mix(z0, z1, posX - posIntX);
- } else {
- x0 = z0;
- }
-
- return x0;
+ // The vertex position
+ // We add a half-texel offset since we are sampling points within a color texture
+ final float x = quad.getX(vertexIndex) - 0.5f;
+ final float y = quad.getY(vertexIndex) - 0.5f;
+ final float z = quad.getZ(vertexIndex) - 0.5f;
+
+ // Integer component of vertex position
+ final int intX = Mth.floor(x);
+ final int intY = Mth.floor(y);
+ final int intZ = Mth.floor(z);
+
+ // Fractional component of vertex position
+ final float fracX = x - intX;
+ final float fracY = y - intY;
+ final float fracZ = z - intZ;
+
+ // Block coordinates (in world space) which the vertex is located within
+ // This is calculated after converting from floating point to avoid precision loss with large coordinates
+ final int blockX = pos.getX() + intX;
+ final int blockY = pos.getY() + intY;
+ final int blockZ = pos.getZ() + intZ;
+
+ // Retrieve the color values for each neighboring value
+ // This creates a 2x2 matrix which is then sampled during interpolation
+ final int m00 = this.getColor(slice, state, scratchPos.set(blockX + 0, blockY, blockZ + 0));
+ final int m01 = this.getColor(slice, state, scratchPos.set(blockX + 0, blockY, blockZ + 1));
+ final int m10 = this.getColor(slice, state, scratchPos.set(blockX + 1, blockY, blockZ + 0));
+ final int m11 = this.getColor(slice, state, scratchPos.set(blockX + 1, blockY, blockZ + 1));
+
+ // Perform interpolation across the X-axis, and then Y-axis
+ // y0 = (m00 * (1.0 - x)) + (m10 * x)
+ // y1 = (m01 * (1.0 - x)) + (m11 * x)
+ // result = (y0 * (1.0 - y)) + (y1 * y)
+ return ColorMixer.mix2d(m00, m01, m10, m11, fracX, fracZ);
}
protected abstract int getColor(LevelSlice slice, T state, BlockPos pos);
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java
index 428b7eed1c..c2e72fb2e4 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java
@@ -22,6 +22,7 @@
import net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess;
import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
import net.caffeinemc.mods.sodium.client.world.LevelRendererExtension;
+import net.caffeinemc.mods.sodium.mixin.core.render.world.EntityRendererAccessor;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
@@ -31,11 +32,14 @@
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
+import net.minecraft.client.renderer.entity.EntityRenderer;
+import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.BlockDestructionProgress;
import net.minecraft.util.Mth;
+import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -172,7 +176,7 @@ public void setupTerrain(Camera camera,
this.reload();
}
- ProfilerFiller profiler = this.client.getProfiler();
+ ProfilerFiller profiler = Profiler.get();
profiler.push("camera_setup");
LocalPlayer player = this.client.player;
@@ -186,7 +190,7 @@ public void setupTerrain(Camera camera,
Matrix4f projectionMatrix = new Matrix4f(RenderSystem.getProjectionMatrix());
float pitch = camera.getXRot();
float yaw = camera.getYRot();
- float fogDistance = RenderSystem.getShaderFogEnd();
+ float fogDistance = RenderSystem.getShaderFog().end();
if (this.lastCameraPos == null) {
this.lastCameraPos = new Vector3d(pos);
@@ -476,7 +480,7 @@ public void iterateVisibleBlockEntities(Consumer blockEntityConsume
* Returns whether or not the entity intersects with any visible chunks in the graph.
* @return True if the entity is visible, otherwise false
*/
- public boolean isEntityVisible(Entity entity) {
+ public boolean isEntityVisible(EntityRenderer renderer, T entity) {
if (!this.useEntityCulling) {
return true;
}
@@ -486,7 +490,7 @@ public boolean isEntityVisible(Entity entity) {
return true;
}
- AABB bb = entity.getBoundingBoxForCulling();
+ AABB bb = ((EntityRendererAccessor) renderer).getCullingBox(entity);
// bail on very large entities to avoid checking many sections
double entityVolume = (bb.maxX - bb.minX) * (bb.maxY - bb.minY) * (bb.maxZ - bb.minZ);
@@ -501,7 +505,7 @@ public boolean isEntityVisible(Entity entity) {
public boolean isBoxVisible(double x1, double y1, double z1, double x2, double y2, double z2) {
// Boxes outside the valid level height will never map to a rendered chunk
// Always render these boxes, or they'll be culled incorrectly!
- if (y2 < this.level.getMinBuildHeight() + 0.5D || y1 > this.level.getMaxBuildHeight() - 0.5D) {
+ if (y2 < this.level.getMinY() + 0.5D || y1 > this.level.getMaxY() - 0.5D) {
return true;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java
index 19b131f4e0..cee84de9c7 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java
@@ -1,7 +1,6 @@
package net.caffeinemc.mods.sodium.client.render.chunk;
import net.caffeinemc.mods.sodium.client.SodiumClientMod;
-import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexAttributeBinding;
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
import net.caffeinemc.mods.sodium.client.gl.device.DrawCommandList;
import net.caffeinemc.mods.sodium.client.gl.device.MultiDrawBatch;
@@ -16,13 +15,13 @@
import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderList;
import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderListIterable;
import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
-import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderBindingPoints;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderInterface;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform;
import net.caffeinemc.mods.sodium.client.util.BitwiseMath;
+import net.caffeinemc.mods.sodium.client.util.UInt32;
import org.lwjgl.system.MemoryUtil;
import java.util.Iterator;
@@ -71,7 +70,7 @@ public void render(ChunkRenderMatrices matrices,
continue;
}
- fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling);
+ fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling, useIndexedTessellation);
if (this.batch.isEmpty()) {
continue;
@@ -108,7 +107,8 @@ private static void fillCommandBuffer(MultiDrawBatch batch,
ChunkRenderList renderList,
CameraTransform camera,
TerrainRenderPass pass,
- boolean useBlockFaceCulling) {
+ boolean useBlockFaceCulling,
+ boolean useIndexedTessellation) {
batch.clear();
var iterator = renderList.sectionsWithGeometryIterator(pass.isTranslucent());
@@ -148,44 +148,48 @@ private static void fillCommandBuffer(MultiDrawBatch batch,
continue;
}
- addDrawCommands(batch, pMeshData, slices);
- }
- }
-
- /**
- * Add the draw command into the multi draw batch of the current region for one
- * section. The section's mesh data is given as a pointer into the render data
- * storage's allocated memory. It goes through each direction and writes the
- * offsets and lengths of the already uploaded vertex and index data. The multi
- * draw batch provides pointers to arrays where each of the section's data is
- * stored. The batch's size counts how many commands it contains.
- */
- private static void addDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) {
- int elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData);
-
- // If high bit is set, the indices should be sourced from the arena's index buffer
- if ((elementOffset & SectionRenderDataUnsafe.BASE_ELEMENT_MSB) != 0) {
- addIndexedDrawCommands(batch, pMeshData, mask);
- } else {
- addNonIndexedDrawCommands(batch, pMeshData, mask);
+ // it's necessary to sometimes not the locally-indexed command generator even for indexed tessellations since
+ // sometimes the index buffer is shared, but not globally shared. This means that translucent sections that
+ // are sharing an index buffer amongst them need to use the shared index command generator since it sets the
+ // same element offset for each draw command and doesn't increment it. Recall that in each draw command the indexing
+ // of the elements needs to start at 0 and thus starting somewhere further into the shared index buffer is invalid.
+ // there's also the optimization that draw commands can be combined when using a shared index buffer, be it
+ // globally shared or just shared within the region, which isn't possible with the locally-indexed command generator.
+ if (useIndexedTessellation && SectionRenderDataUnsafe.isLocalIndex(pMeshData)) {
+ addLocalIndexedDrawCommands(batch, pMeshData, slices);
+ } else {
+ addSharedIndexedDrawCommands(batch, pMeshData, slices);
+ }
}
}
/**
- * Generates the draw commands for a chunk's meshes using the shared index buffer.
+ * Generates the draw commands for a chunk's meshes, where each mesh has a separate index buffer. This is used
+ * when rendering translucent geometry, as each geometry set needs a sorted index buffer.
*/
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
- private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) {
+ private static void addLocalIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) {
final var pElementPointer = batch.pElementPointer;
final var pBaseVertex = batch.pBaseVertex;
final var pElementCount = batch.pElementCount;
int size = batch.size;
+ long elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData);
+ long baseVertex = SectionRenderDataUnsafe.getBaseVertex(pMeshData);
+
for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) {
- MemoryUtil.memPutInt(pBaseVertex + (size << 2), SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing));
- MemoryUtil.memPutInt(pElementCount + (size << 2), SectionRenderDataUnsafe.getElementCount(pMeshData, facing));
- MemoryUtil.memPutAddress(pElementPointer + (size << 3), 0 /* using a shared index buffer */);
+ final long vertexCount = SectionRenderDataUnsafe.getVertexCount(pMeshData, facing);
+ final long elementCount = (vertexCount >> 2) * 6;
+
+ MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast(elementCount));
+ MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(baseVertex));
+
+ // * 4 to convert to bytes (the index buffer contains integers)
+ MemoryUtil.memPutAddress(pElementPointer + (size << 3), elementOffset << 2);
+
+ baseVertex += vertexCount;
+ elementOffset += elementCount;
size += (mask >> facing) & 1;
}
@@ -194,33 +198,57 @@ private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshDa
}
/**
- * Generates the draw commands for a chunk's meshes, where each mesh has a separate index buffer. This is used
- * when rendering translucent geometry, as each geometry set needs a sorted index buffer.
+ * Generates the draw commands for a chunk's meshes using the shared index buffer.
*/
@SuppressWarnings("IntegerMultiplicationImplicitCastToLong")
- private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) {
+ private static void addSharedIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) {
final var pElementPointer = batch.pElementPointer;
final var pBaseVertex = batch.pBaseVertex;
final var pElementCount = batch.pElementCount;
- int size = batch.size;
-
- int elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData)
- & ~SectionRenderDataUnsafe.BASE_ELEMENT_MSB;
-
- for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) {
- final var elementCount = SectionRenderDataUnsafe.getElementCount(pMeshData, facing);
+ // this is either zero (global shared index buffer) or the offset to the location of the shared element buffer (region shared index buffer)
+ final var elementOffsetBytes = SectionRenderDataUnsafe.getBaseElement(pMeshData) << 2;
+ final var facingList = SectionRenderDataUnsafe.getFacingList(pMeshData);
- MemoryUtil.memPutInt(pBaseVertex + (size << 2), SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing));
- MemoryUtil.memPutInt(pElementCount + (size << 2), elementCount);
+ int size = batch.size;
+ long groupVertexCount = 0;
+ long baseVertex = SectionRenderDataUnsafe.getBaseVertex(pMeshData);
+ int lastMaskBit = 0;
+
+ for (int i = 0; i <= ModelQuadFacing.COUNT; i++) {
+ var maskBit = 0;
+ long vertexCount = 0;
+ if (i < ModelQuadFacing.COUNT) {
+ vertexCount = SectionRenderDataUnsafe.getVertexCount(pMeshData, i);
+
+ // if there's no vertexes, the mask bit is just 0
+ if (vertexCount != 0) {
+ var facing = (facingList >>> (i * 8)) & 0xFF;
+ maskBit = (mask >>> facing) & 1;
+ }
+ }
- // * 4 to convert to bytes (the buffer contains 32-bit integers)
- // the section render data storage for the indices stores the offset in indices (also called elements)
- MemoryUtil.memPutAddress(pElementPointer + (size << 3), elementOffset << 2);
+ if (maskBit == 0) {
+ if (lastMaskBit == 1) {
+ // delay writing out draw command if there's a zero-size group
+ if (i < ModelQuadFacing.COUNT && vertexCount == 0) {
+ continue;
+ }
+
+ MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast((groupVertexCount >> 2) * 6));
+ MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(baseVertex));
+ MemoryUtil.memPutAddress(pElementPointer + (size << 3), elementOffsetBytes);
+ size++;
+ baseVertex += groupVertexCount;
+ groupVertexCount = 0;
+ }
+
+ baseVertex += vertexCount;
+ } else {
+ groupVertexCount += vertexCount;
+ }
- // adding the number of elements works because the index data has one index per element (which are the indices)
- elementOffset += elementCount;
- size += (mask >> facing) & 1;
+ lastMaskBit = maskBit;
}
batch.size = size;
@@ -235,7 +263,7 @@ private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData,
private static final int MODEL_NEG_Y = ModelQuadFacing.NEG_Y.ordinal();
private static final int MODEL_NEG_Z = ModelQuadFacing.NEG_Z.ordinal();
- private static int getVisibleFaces(int originX, int originY, int originZ, int chunkX, int chunkY, int chunkZ) {
+ public static int getVisibleFaces(int originX, int originY, int originZ, int chunkX, int chunkY, int chunkZ) {
// This is carefully written so that we can keep everything branch-less.
//
// Normally, this would be a ridiculous way to handle the problem. But the Hotspot VM's
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java
index 0103850155..9aa933474c 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java
@@ -69,9 +69,9 @@ public RenderSection(RenderRegion region, int chunkX, int chunkY, int chunkZ) {
this.chunkY = chunkY;
this.chunkZ = chunkZ;
- int rX = this.getChunkX() & (RenderRegion.REGION_WIDTH - 1);
- int rY = this.getChunkY() & (RenderRegion.REGION_HEIGHT - 1);
- int rZ = this.getChunkZ() & (RenderRegion.REGION_LENGTH - 1);
+ int rX = this.getChunkX() & RenderRegion.REGION_WIDTH_M;
+ int rY = this.getChunkY() & RenderRegion.REGION_HEIGHT_M;
+ int rZ = this.getChunkZ() & RenderRegion.REGION_LENGTH_M;
this.sectionIndex = LocalSectionIndex.pack(rX, rY, rZ);
@@ -139,30 +139,44 @@ public void delete() {
this.disposed = true;
}
- public void setInfo(@Nullable BuiltSectionInfo info) {
+ public boolean setInfo(@Nullable BuiltSectionInfo info) {
if (info != null) {
- this.setRenderState(info);
+ return this.setRenderState(info);
} else {
- this.clearRenderState();
+ return this.clearRenderState();
}
}
- private void setRenderState(@NotNull BuiltSectionInfo info) {
+ private boolean setRenderState(@NotNull BuiltSectionInfo info) {
+ var prevBuilt = this.built;
+ var prevFlags = this.flags;
+ var prevVisibilityData = this.visibilityData;
+
this.built = true;
this.flags = info.flags;
this.visibilityData = info.visibilityData;
+
this.globalBlockEntities = info.globalBlockEntities;
this.culledBlockEntities = info.culledBlockEntities;
this.animatedSprites = info.animatedSprites;
+
+ // the section is marked as having received graph-relevant changes if it's build state, flags, or connectedness has changed.
+ // the entities and sprites don't need to be checked since whether they exist is encoded in the flags.
+ return !prevBuilt || prevFlags != this.flags || prevVisibilityData != this.visibilityData;
}
- private void clearRenderState() {
+ private boolean clearRenderState() {
+ var wasBuilt = this.built;
+
this.built = false;
this.flags = RenderSectionFlags.NONE;
this.visibilityData = VisibilityEncoding.NULL;
this.globalBlockEntities = null;
this.culledBlockEntities = null;
this.animatedSprites = null;
+
+ // changes to data if it moves from built to not built don't matter, so only build state changes matter
+ return wasBuilt;
}
/**
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java
index 21aae7a7e1..97f57148f7 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java
@@ -146,7 +146,7 @@ private void createTerrainRenderList(Camera camera, Viewport viewport, int frame
this.occlusionCuller.findVisible(visitor, viewport, searchDistance, useOcclusionCulling, frame);
- this.renderLists = visitor.createRenderLists();
+ this.renderLists = visitor.createRenderLists(viewport);
this.taskLists = visitor.getRebuildLists();
}
@@ -167,7 +167,7 @@ private boolean shouldUseOcclusionCulling(Camera camera, boolean spectator) {
BlockPos origin = camera.getBlockPosition();
if (spectator && this.level.getBlockState(origin)
- .isSolidRender(this.level, origin))
+ .isSolidRender())
{
useOcclusionCulling = false;
} else {
@@ -209,6 +209,7 @@ public void onSectionAdded(int x, int y, int z) {
this.connectNeighborNodes(renderSection);
+ // force update to schedule build task
this.needsGraphUpdate = true;
}
@@ -235,6 +236,7 @@ public void onSectionRemoved(int x, int y, int z) {
section.delete();
+ // force update to remove section from render lists
this.needsGraphUpdate = true;
}
@@ -301,7 +303,7 @@ public void uploadChunks() {
// (sort results never change the graph)
// generally there's no sort results without a camera movement, which would also trigger
// a graph update, but it can sometimes happen because of async task execution
- this.needsGraphUpdate = this.needsGraphUpdate || this.processChunkBuildResults(results);
+ this.needsGraphUpdate |= this.processChunkBuildResults(results);
for (var result : results) {
result.destroy();
@@ -317,8 +319,7 @@ private boolean processChunkBuildResults(ArrayList results) {
for (var result : filtered) {
TranslucentData oldData = result.render.getTranslucentData();
if (result instanceof ChunkBuildOutput chunkBuildOutput) {
- this.updateSectionInfo(result.render, chunkBuildOutput.info);
- touchedSectionInfo = true;
+ touchedSectionInfo |= this.updateSectionInfo(result.render, chunkBuildOutput.info);
if (chunkBuildOutput.translucentData != null) {
this.sortTriggering.integrateTranslucentData(oldData, chunkBuildOutput.translucentData, this.cameraPosition, this::scheduleSort);
@@ -327,9 +328,9 @@ private boolean processChunkBuildResults(ArrayList results) {
result.render.setTranslucentData(chunkBuildOutput.translucentData);
}
} else if (result instanceof ChunkSortOutput sortOutput
- && sortOutput.getTopoSorter() != null
+ && sortOutput.getDynamicSorter() != null
&& result.render.getTranslucentData() instanceof DynamicTopoData data) {
- this.sortTriggering.applyTriggerChanges(data, sortOutput.getTopoSorter(), result.render.getPosition(), this.cameraPosition);
+ this.sortTriggering.applyTriggerChanges(data, sortOutput.getDynamicSorter(), result.render.getPosition(), this.cameraPosition);
}
var job = result.render.getTaskCancellationToken();
@@ -346,13 +347,13 @@ private boolean processChunkBuildResults(ArrayList results) {
return touchedSectionInfo;
}
- private void updateSectionInfo(RenderSection render, BuiltSectionInfo info) {
- render.setInfo(info);
+ private boolean updateSectionInfo(RenderSection render, BuiltSectionInfo info) {
+ var infoChanged = render.setInfo(info);
if (info == null || ArrayUtils.isEmpty(info.globalBlockEntities)) {
- this.sectionsWithGlobalEntities.remove(render);
+ return this.sectionsWithGlobalEntities.remove(render) || infoChanged;
} else {
- this.sectionsWithGlobalEntities.add(render);
+ return this.sectionsWithGlobalEntities.add(render) || infoChanged;
}
}
@@ -609,6 +610,7 @@ public void scheduleRebuild(int x, int y, int z, boolean important) {
if (pendingUpdate != null) {
section.setPendingUpdate(pendingUpdate);
+ // force update to schedule rebuild task on this section
this.needsGraphUpdate = true;
}
}
@@ -626,13 +628,13 @@ private static boolean allowImportantRebuilds() {
}
private float getEffectiveRenderDistance() {
- var color = RenderSystem.getShaderFogColor();
- var distance = RenderSystem.getShaderFogEnd();
+ var alpha = RenderSystem.getShaderFog().alpha();
+ var distance = RenderSystem.getShaderFog().end();
var renderDistance = this.getRenderDistance();
// The fog must be fully opaque in order to skip rendering of chunks behind it
- if (!Mth.equal(color[3], 1.0f)) {
+ if (!Mth.equal(alpha, 1.0f)) {
return renderDistance;
}
@@ -676,8 +678,10 @@ public Collection getDebugStrings() {
int count = 0;
- long deviceUsed = 0;
- long deviceAllocated = 0;
+ long geometryDeviceUsed = 0;
+ long geometryDeviceAllocated = 0;
+ long indexDeviceUsed = 0;
+ long indexDeviceAllocated = 0;
for (var region : this.regions.getLoadedRegions()) {
var resources = region.getResources();
@@ -686,15 +690,19 @@ public Collection getDebugStrings() {
continue;
}
- var buffer = resources.getGeometryArena();
+ var geometryArena = resources.getGeometryArena();
+ geometryDeviceUsed += geometryArena.getDeviceUsedMemory();
+ geometryDeviceAllocated += geometryArena.getDeviceAllocatedMemory();
- deviceUsed += buffer.getDeviceUsedMemory();
- deviceAllocated += buffer.getDeviceAllocatedMemory();
+ var indexArena = resources.getIndexArena();
+ indexDeviceUsed += indexArena.getDeviceUsedMemory();
+ indexDeviceAllocated += indexArena.getDeviceAllocatedMemory();
count++;
}
- list.add(String.format("Geometry Pool: %d/%d MiB (%d buffers)", MathUtil.toMib(deviceUsed), MathUtil.toMib(deviceAllocated), count));
+ list.add(String.format("Geometry Pool: %d/%d MiB (%d buffers)", MathUtil.toMib(geometryDeviceUsed), MathUtil.toMib(geometryDeviceAllocated), count));
+ list.add(String.format("Index Pool: %d/%d MiB", MathUtil.toMib(indexDeviceUsed), MathUtil.toMib(indexDeviceAllocated)));
list.add(String.format("Transfer Queue: %s", this.regions.getStagingBuffer().toString()));
list.add(String.format("Chunk Builder: Permits=%02d (E %03d) | Busy=%02d | Total=%02d",
@@ -723,13 +731,13 @@ public boolean isSectionBuilt(int x, int y, int z) {
}
public void onChunkAdded(int x, int z) {
- for (int y = this.level.getMinSection(); y < this.level.getMaxSection(); y++) {
+ for (int y = this.level.getMinSectionY(); y <= this.level.getMaxSectionY(); y++) {
this.onSectionAdded(x, y, z);
}
}
public void onChunkRemoved(int x, int z) {
- for (int y = this.level.getMinSection(); y < this.level.getMaxSection(); y++) {
+ for (int y = this.level.getMinSectionY(); y <= this.level.getMaxSectionY(); y++) {
this.onSectionRemoved(x, y, z);
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java
index fecce3ba8f..675e8ae632 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java
@@ -42,7 +42,7 @@ private GlProgram createShader(String path, ChunkShaderOpt
GlShader vertShader = ShaderLoader.loadShader(ShaderType.VERTEX,
ResourceLocation.fromNamespaceAndPath("sodium", path + ".vsh"), constants);
-
+
GlShader fragShader = ShaderLoader.loadShader(ShaderType.FRAGMENT,
ResourceLocation.fromNamespaceAndPath("sodium", path + ".fsh"), constants);
@@ -50,8 +50,7 @@ private GlProgram createShader(String path, ChunkShaderOpt
return GlProgram.builder(ResourceLocation.fromNamespaceAndPath("sodium", "chunk_shader"))
.attachShader(vertShader)
.attachShader(fragShader)
- .bindAttribute("a_PositionHi", ChunkShaderBindingPoints.ATTRIBUTE_POSITION_HI)
- .bindAttribute("a_PositionLo", ChunkShaderBindingPoints.ATTRIBUTE_POSITION_LO)
+ .bindAttribute("a_Position", ChunkShaderBindingPoints.ATTRIBUTE_POSITION)
.bindAttribute("a_Color", ChunkShaderBindingPoints.ATTRIBUTE_COLOR)
.bindAttribute("a_TexCoord", ChunkShaderBindingPoints.ATTRIBUTE_TEXTURE)
.bindAttribute("a_LightAndData", ChunkShaderBindingPoints.ATTRIBUTE_LIGHT_MATERIAL_INDEX)
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java
index e21dc96476..1526d6408f 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java
@@ -7,6 +7,7 @@
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
import net.caffeinemc.mods.sodium.client.gl.tessellation.GlIndexType;
import net.caffeinemc.mods.sodium.client.gl.util.EnumBitField;
+import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
@@ -55,6 +56,14 @@ private void grow(CommandList commandList, int primitiveCount) {
this.maxPrimitives = primitiveCount;
}
+ public static NativeBuffer createIndexBuffer(IndexType indexType, int primitiveCount) {
+ var bufferSize = primitiveCount * indexType.getBytesPerElement() * ELEMENTS_PER_PRIMITIVE;
+ var buffer = new NativeBuffer(bufferSize);
+
+ indexType.createIndexBuffer(buffer.getDirectBuffer(), primitiveCount);
+
+ return buffer;
+ }
public GlBuffer getBufferObject() {
return this.buffer;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java
index eae5389deb..189a5ad3ff 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java
@@ -6,6 +6,7 @@
import net.caffeinemc.mods.sodium.client.render.chunk.compile.buffers.ChunkModelBuilder;
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo;
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts;
+import net.caffeinemc.mods.sodium.client.render.chunk.data.SectionRenderDataUnsafe;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material;
@@ -13,10 +14,6 @@
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* A collection of temporary buffers for each worker thread which will be used to build chunk meshes for given render
* passes. This makes a best-effort attempt to pick a suitable size for each scratch buffer, but will never try to
@@ -60,47 +57,79 @@ public ChunkModelBuilder get(TerrainRenderPass pass) {
* have been rendered to pass the finished meshes over to the graphics card. This function can be called multiple
* times to return multiple copies.
*/
- public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, boolean forceUnassigned) {
+ public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, int visibleSlices, boolean forceUnassigned, boolean sliceReordering) {
var builder = this.builders.get(pass);
+ int[] vertexSegments = new int[ModelQuadFacing.COUNT << 1];
+ int vertexTotal = 0;
- List vertexBuffers = new ArrayList<>();
- int[] vertexCounts = new int[ModelQuadFacing.COUNT];
+ // get the total vertex count to initialize the buffer
+ for (ModelQuadFacing facing : ModelQuadFacing.VALUES) {
+ vertexTotal += builder.getVertexBuffer(facing).count();
+ }
- int vertexSum = 0;
+ if (vertexTotal == 0) {
+ return null;
+ }
- for (ModelQuadFacing facing : ModelQuadFacing.VALUES) {
- var ordinal = facing.ordinal();
- var buffer = builder.getVertexBuffer(facing);
+ var mergedBuffer = new NativeBuffer(vertexTotal * this.vertexType.getVertexFormat().getStride());
+ var mergedBufferBuilder = mergedBuffer.getDirectBuffer();
- if (buffer.isEmpty()) {
- continue;
+ if (sliceReordering) {
+ // sliceReordering implies !forceUnassigned
+
+ // write all currently visible slices first, and then the rest.
+ // start with unassigned as it will never become invisible
+ var unassignedBuffer = builder.getVertexBuffer(ModelQuadFacing.UNASSIGNED);
+ int vertexSegmentCount = 0;
+ vertexSegments[vertexSegmentCount++] = unassignedBuffer.count();
+ vertexSegments[vertexSegmentCount++] = ModelQuadFacing.UNASSIGNED.ordinal();
+ if (!unassignedBuffer.isEmpty()) {
+ mergedBufferBuilder.put(unassignedBuffer.slice());
}
- vertexBuffers.add(buffer.slice());
- var bufferCount = buffer.count();
- if (!forceUnassigned) {
- vertexCounts[ordinal] = bufferCount;
- }
+ // write all visible and then invisible slices
+ for (var step = 0; step < 2; step++) {
+ for (ModelQuadFacing facing : ModelQuadFacing.VALUES) {
+ var facingIndex = facing.ordinal();
+ if (facing == ModelQuadFacing.UNASSIGNED || ((visibleSlices >> facingIndex) & 1) == step) {
+ continue;
+ }
- vertexSum += bufferCount;
- }
+ var buffer = builder.getVertexBuffer(facing);
- if (vertexSum == 0) {
- return null;
- }
+ // generate empty ranges to prevent SectionRenderData storage from making up indexes for null ranges
+ vertexSegments[vertexSegmentCount++] = buffer.count();
+ vertexSegments[vertexSegmentCount++] = facingIndex;
- if (forceUnassigned) {
- vertexCounts[ModelQuadFacing.UNASSIGNED.ordinal()] = vertexSum;
- }
+ if (!buffer.isEmpty()) {
+ mergedBufferBuilder.put(buffer.slice());
+ }
+ }
+ }
+ } else {
+ // forceUnassigned implies !sliceReordering
- var mergedBuffer = new NativeBuffer(vertexSum * this.vertexType.getVertexFormat().getStride());
- var mergedBufferBuilder = mergedBuffer.getDirectBuffer();
+ if (forceUnassigned) {
+ var segmentIndex = ModelQuadFacing.UNASSIGNED.ordinal() << 1;
+ vertexSegments[segmentIndex] = vertexTotal;
+ vertexSegments[segmentIndex + 1] = ModelQuadFacing.UNASSIGNED.ordinal();
+ }
- for (var buffer : vertexBuffers) {
- mergedBufferBuilder.put(buffer);
+ for (ModelQuadFacing facing : ModelQuadFacing.VALUES) {
+ var buffer = builder.getVertexBuffer(facing);
+ if (!buffer.isEmpty()) {
+ if (!forceUnassigned) {
+ var facingIndex = facing.ordinal();
+ var segmentIndex = facingIndex << 1;
+ vertexSegments[segmentIndex] = buffer.count();
+ vertexSegments[segmentIndex + 1] = facingIndex;
+ }
+ mergedBufferBuilder.put(buffer.slice());
+ }
+ }
}
- return new BuiltSectionMeshParts(mergedBuffer, vertexCounts);
+ return new BuiltSectionMeshParts(mergedBuffer, vertexSegments);
}
public void destroy() {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java
index 52236a161e..1e17585537 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java
@@ -2,14 +2,11 @@
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData;
-import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.SortData;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.Sorter;
-import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
-public class ChunkSortOutput extends BuilderTaskOutput implements SortData {
- private NativeBuffer indexBuffer;
+public class ChunkSortOutput extends BuilderTaskOutput {
+ private Sorter sorter;
private boolean reuseUploadedIndexData;
- private DynamicTopoData.DynamicTopoSorter topoSorter;
public ChunkSortOutput(RenderSection render, int buildTime) {
super(render, buildTime);
@@ -17,41 +14,34 @@ public ChunkSortOutput(RenderSection render, int buildTime) {
public ChunkSortOutput(RenderSection render, int buildTime, Sorter data) {
this(render, buildTime);
- this.copyResultFrom(data);
+ this.setSorter(data);
}
- public void copyResultFrom(Sorter sorter) {
- this.indexBuffer = sorter.getIndexBuffer();
+ public void setSorter(Sorter sorter) {
+ this.sorter = sorter;
this.reuseUploadedIndexData = false;
- if (sorter instanceof DynamicTopoData.DynamicTopoSorter topoSorterInstance) {
- this.topoSorter = topoSorterInstance;
- }
}
- public void markAsReusingUploadedData() {
- this.reuseUploadedIndexData = true;
+ public Sorter getSorter() {
+ return this.sorter;
}
- @Override
- public NativeBuffer getIndexBuffer() {
- return this.indexBuffer;
+ public void markAsReusingUploadedData() {
+ this.reuseUploadedIndexData = true;
}
- @Override
public boolean isReusingUploadedIndexData() {
return this.reuseUploadedIndexData;
}
- public DynamicTopoData.DynamicTopoSorter getTopoSorter() {
- return this.topoSorter;
+ public DynamicTopoData.DynamicTopoSorter getDynamicSorter() {
+ return this.sorter instanceof DynamicTopoData.DynamicTopoSorter dynamicSorter ? dynamicSorter : null;
}
- @Override
public void destroy() {
super.destroy();
-
- if (this.indexBuffer != null) {
- this.indexBuffer.free();
+ if (this.sorter != null) {
+ this.sorter.destroy();
}
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java
index 792903a5b5..e251f27e89 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java
@@ -1,10 +1,13 @@
package net.caffeinemc.mods.sodium.client.render.chunk.compile.executor;
+import com.mojang.jtracy.TracyClient;
+import com.mojang.jtracy.Zone;
import net.caffeinemc.mods.sodium.client.SodiumClientMod;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.BuilderTaskOutput;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderTask;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
+import net.minecraft.SharedConstants;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.util.Mth;
import org.apache.commons.lang3.Validate;
@@ -48,7 +51,7 @@ public ChunkBuilder(ClientLevel level, ChunkVertexType vertexType) {
for (int i = 0; i < count; i++) {
ChunkBuildContext context = new ChunkBuildContext(level, vertexType);
- WorkerRunnable worker = new WorkerRunnable(context);
+ WorkerRunnable worker = new WorkerRunnable("Chunk Render Task Executor #" + i, context);
Thread thread = new Thread(worker, "Chunk Render Task Executor #" + i);
thread.setPriority(Math.max(0, Thread.NORM_PRIORITY - 2));
@@ -184,9 +187,11 @@ public int getTotalThreadCount() {
private class WorkerRunnable implements Runnable {
// Making this thread-local provides a small boost to performance by avoiding the overhead in synchronizing
// caches between different CPU cores
+ private final String name;
private final ChunkBuildContext context;
- public WorkerRunnable(ChunkBuildContext context) {
+ public WorkerRunnable(String name, ChunkBuildContext context) {
+ this.name = name;
this.context = context;
}
@@ -209,6 +214,8 @@ public void run() {
ChunkBuilder.this.busyThreadCount.getAndIncrement();
+ Zone zone = TracyClient.beginZone(name, SharedConstants.IS_RUNNING_IN_IDE);
+
try {
job.execute(this.context);
} finally {
@@ -216,6 +223,8 @@ public void run() {
ChunkBuilder.this.busyThreadCount.decrementAndGet();
}
+
+ zone.close();
}
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java
index 294b2b96e4..f157fac5b1 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java
@@ -8,6 +8,7 @@
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
@@ -30,53 +31,131 @@ public BlockOcclusionCache() {
}
/**
- * @param selfState The state of the block in the level
- * @param view The block view for this render context
- * @param selfPos The position of the block
- * @param facing The facing direction of the side to check
+ * @param selfBlockState The state of the block in the level
+ * @param view The block view for this render context
+ * @param selfPos The position of the block
+ * @param facing The facing direction of the side to check
* @return True if the block side facing {@param dir} is not occluded, otherwise false
*/
- public boolean shouldDrawSide(BlockState selfState, BlockGetter view, BlockPos selfPos, Direction facing) {
- BlockPos.MutableBlockPos otherPos = this.cachedPositionObject;
- otherPos.set(selfPos.getX() + facing.getStepX(), selfPos.getY() + facing.getStepY(), selfPos.getZ() + facing.getStepZ());
+ public boolean shouldDrawSide(BlockState selfBlockState, BlockGetter view, BlockPos selfPos, Direction facing) {
+ BlockPos.MutableBlockPos neighborPos = this.cachedPositionObject;
+ neighborPos.setWithOffset(selfPos, facing);
- BlockState otherState = view.getBlockState(otherPos);
+ // The block state of the neighbor
+ BlockState neighborBlockState = view.getBlockState(neighborPos);
+
+ // The cull shape of the neighbor between the block being rendered and it
+ VoxelShape neighborShape = neighborBlockState.getFaceOcclusionShape(DirectionUtil.getOpposite(facing));
+
+ // Minecraft enforces that if the neighbor has a full-block occlusion shape, the face is always hidden
+ if (isFullShape(neighborShape)) {
+ return false;
+ }
- // Blocks can define special behavior to control whether faces are rendered.
+ // Blocks can define special behavior to control whether their faces are rendered.
// This is mostly used by transparent blocks (Leaves, Glass, etc.) to not render interior faces between blocks
// of the same type.
- if (selfState.skipRendering(otherState, facing) || PlatformBlockAccess.getInstance().shouldSkipRender(view, selfState, otherState, selfPos, otherPos, facing)) {
+ if (selfBlockState.skipRendering(neighborBlockState, facing)) {
+ return false;
+ } else if (PlatformBlockAccess.getInstance()
+ .shouldSkipRender(view, selfBlockState, neighborBlockState, selfPos, neighborPos, facing)) {
return false;
}
- // If the other block is transparent, then it is unable to hide any geometry.
- if (!otherState.canOcclude()) {
+ // After any custom behavior has been handled, check if the neighbor block is transparent or has an empty
+ // cull shape. These blocks cannot hide any geometry.
+ if (isEmptyShape(neighborShape) || !neighborBlockState.canOcclude()) {
return true;
}
- // The cull shape of the block being rendered
- VoxelShape selfShape = selfState.getFaceOcclusionShape(view, selfPos, facing);
+ // The cull shape between of the block being rendered, between it and the neighboring block
+ VoxelShape selfShape = selfBlockState.getFaceOcclusionShape(facing);
- // If the block being rendered has an empty cull shape, intersection tests will always fail
- if (selfShape.isEmpty()) {
+ // If the block being rendered has an empty cull shape, there will be no intersection with the neighboring
+ // block's cull shape, so no geometry can be hidden.
+ if (isEmptyShape(selfShape)) {
return true;
}
- // The cull shape of the block neighboring the one being rendered
- VoxelShape otherShape = otherState.getFaceOcclusionShape(view, otherPos, DirectionUtil.getOpposite(facing));
+ // No other simplifications apply, so we need to perform a full shape comparison, which is very slow
+ return this.lookup(selfShape, neighborShape);
+ }
- // If the other block has an empty cull shape, then it cannot hide any geometry
- if (otherShape.isEmpty()) {
- return true;
+ private static boolean isFullShape(VoxelShape selfShape) {
+ return selfShape == Shapes.block();
+ }
+
+ private static boolean isEmptyShape(VoxelShape voxelShape) {
+ return voxelShape == Shapes.empty() || voxelShape.isEmpty();
+ }
+
+ /**
+ * Checks if a face of a fluid block should be rendered. It takes into account both occluding fluid face against its own waterlogged block state and the neighboring block state. This is an approximation that doesn't check voxel for shapes between the fluid and the neighboring block since that is handled by the fluid renderer separately and more accurately using actual fluid heights. It only uses voxel shape comparison for checking self-occlusion with the waterlogged block state.
+ *
+ * @param selfBlockState The state of the block in the level
+ * @param view The block view for this render context
+ * @param selfPos The position of the fluid
+ * @param facing The facing direction of the side to check
+ * @param fluid The fluid state
+ * @param fluidShape The non-empty shape of the fluid
+ * @return True if the fluid side facing {@param dir} is not occluded, otherwise false
+ */
+ public boolean shouldDrawFullBlockFluidSide(BlockState selfBlockState, BlockGetter view, BlockPos selfPos, Direction facing, FluidState fluid, VoxelShape fluidShape) {
+ var fluidShapeIsBlock = fluidShape == Shapes.block();
+
+ // only perform self-occlusion if the own block state can't occlude
+ if (selfBlockState.canOcclude()) {
+ var selfShape = selfBlockState.getFaceOcclusionShape(facing);
+
+ // only a non-empty self-shape can occlude anything
+ if (!isEmptyShape(selfShape)) {
+ // a full self-shape occludes everything
+ if (isFullShape(selfShape) && fluidShapeIsBlock) {
+ return false;
+ }
+
+ // perform occlusion of the fluid by the block it's contained in
+ if (!this.lookup(fluidShape, selfShape)) {
+ return false;
+ }
+ }
+ }
+
+ // perform occlusion against the neighboring block
+ BlockPos.MutableBlockPos otherPos = this.cachedPositionObject;
+ otherPos.set(selfPos.getX() + facing.getStepX(), selfPos.getY() + facing.getStepY(), selfPos.getZ() + facing.getStepZ());
+ BlockState otherState = view.getBlockState(otherPos);
+
+ // don't render anything if the other blocks is the same fluid
+ if (otherState.getFluidState() == fluid) {
+ return false;
}
- // If both blocks use a full-cube cull shape, then they will always hide the faces between each other
- if (selfShape == Shapes.block() && otherShape == Shapes.block()) {
+ // check for special fluid occlusion behavior
+ if (PlatformBlockAccess.getInstance().shouldOccludeFluid(facing.getOpposite(), otherState, fluid)) {
return false;
}
- // No other simplifications apply, so we need to perform a full shape comparison, which is very slow
- return this.lookup(selfShape, otherShape);
+ // the up direction doesn't do occlusion with other block shapes
+ if (facing == Direction.UP) {
+ return true;
+ }
+
+ // only occlude against blocks that can potentially occlude in the first place
+ if (!otherState.canOcclude()) {
+ return true;
+ }
+
+ var otherShape = otherState.getFaceOcclusionShape(facing.getOpposite());
+
+ // If the other block has an empty cull shape, then it cannot hide any geometry
+ if (isEmptyShape(otherShape)) {
+ return true;
+ }
+
+ // If both blocks use a full-cube cull shape, then they will always hide the faces between each other.
+ // No voxel shape comparison is done after this point because it's redundant with the later more accurate check.
+ return !isFullShape(otherShape) || !fluidShapeIsBlock;
}
private boolean lookup(VoxelShape self, VoxelShape other) {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java
index 23cf5303aa..e042e129d7 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java
@@ -2,6 +2,8 @@
import net.caffeinemc.mods.sodium.api.util.ColorABGR;
import net.caffeinemc.mods.sodium.api.util.ColorARGB;
+import net.caffeinemc.mods.sodium.api.util.ColorMixer;
+import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds;
import net.caffeinemc.mods.sodium.client.model.color.ColorProvider;
import net.caffeinemc.mods.sodium.client.model.color.ColorProviderRegistry;
import net.caffeinemc.mods.sodium.client.model.light.LightMode;
@@ -19,7 +21,6 @@
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.builder.ChunkMeshBufferBuilder;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder;
-import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper;
import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl;
import net.caffeinemc.mods.sodium.client.render.frapi.render.AbstractBlockRenderContext;
import net.caffeinemc.mods.sodium.client.render.texture.SpriteFinderCache;
@@ -84,7 +85,7 @@ public void renderModel(BakedModel model, BlockState state, BlockPos pos, BlockP
this.posOffset.set(origin.getX(), origin.getY(), origin.getZ());
if (state.hasOffsetFunction()) {
- Vec3 modelOffset = state.getOffset(this.level, pos);
+ Vec3 modelOffset = state.getOffset(pos);
this.posOffset.add((float) modelOffset.x, (float) modelOffset.y, (float) modelOffset.z);
}
@@ -101,7 +102,7 @@ public void renderModel(BakedModel model, BlockState state, BlockPos pos, BlockP
for (RenderType type : renderTypes) {
this.type = type;
- ((FabricBakedModel) model).emitBlockQuads(this.level, state, pos, this.randomSupplier, this);
+ ((FabricBakedModel) model).emitBlockQuads(getEmitter(), this.level, state, pos, this.randomSupplier, this::isFaceCulled);
}
type = null;
@@ -114,7 +115,6 @@ public void renderModel(BakedModel model, BlockState state, BlockPos pos, BlockP
@Override
protected void processQuad(MutableQuadViewImpl quad) {
final RenderMaterial mat = quad.material();
- final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex();
final TriState aoMode = mat.ambientOcclusion();
final ShadeMode shadeMode = mat.shadeMode();
final LightMode lightMode;
@@ -134,13 +134,15 @@ protected void processQuad(MutableQuadViewImpl quad) {
material = DefaultMaterials.forRenderLayer(blendMode.blockRenderLayer == null ? type : blendMode.blockRenderLayer);
}
- this.colorizeQuad(quad, colorIndex);
+ this.tintQuad(quad);
this.shadeQuad(quad, lightMode, emissive, shadeMode);
this.bufferQuad(quad, this.quadLightData.br, material);
}
- private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) {
- if (colorIndex != -1) {
+ private void tintQuad(MutableQuadViewImpl quad) {
+ int tintIndex = quad.tintIndex();
+
+ if (tintIndex != -1) {
ColorProvider colorProvider = this.colorProvider;
if (colorProvider != null) {
@@ -148,7 +150,7 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) {
colorProvider.getColors(this.slice, this.pos, this.scratchPos, this.state, quad, vertexColors);
for (int i = 0; i < 4; i++) {
- quad.color(i, ColorHelper.multiplyColor(vertexColors[i], quad.color(i)));
+ quad.color(i, ColorMixer.mulComponentWise(vertexColors[i], quad.color(i)));
}
}
}
@@ -184,6 +186,7 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material
// attempt render pass downgrade if possible
var pass = material.pass;
+
var downgradedPass = attemptPassDowngrade(atlasSprite, pass);
if (downgradedPass != null) {
pass = downgradedPass;
@@ -225,7 +228,11 @@ private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) {
return true;
}
- private TerrainRenderPass attemptPassDowngrade(TextureAtlasSprite sprite, TerrainRenderPass pass) {
+ private @Nullable TerrainRenderPass attemptPassDowngrade(TextureAtlasSprite sprite, TerrainRenderPass pass) {
+ if (Workarounds.isWorkaroundEnabled(Workarounds.Reference.INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE)) {
+ return null;
+ }
+
boolean attemptDowngrade = true;
boolean hasNonOpaqueVertex = false;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java
index b49cde9421..cf89774909 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java
@@ -4,7 +4,6 @@
import net.caffeinemc.mods.sodium.api.util.ColorARGB;
import net.caffeinemc.mods.sodium.api.util.NormI8;
import net.caffeinemc.mods.sodium.client.model.color.ColorProvider;
-import net.caffeinemc.mods.sodium.client.model.color.ColorProviderRegistry;
import net.caffeinemc.mods.sodium.client.model.light.LightMode;
import net.caffeinemc.mods.sodium.client.model.light.LightPipeline;
import net.caffeinemc.mods.sodium.client.model.light.LightPipelineProvider;
@@ -28,8 +27,6 @@
import net.minecraft.tags.FluidTags;
import net.minecraft.util.Mth;
import net.minecraft.world.level.BlockAndTintGetter;
-import net.minecraft.world.level.block.LiquidBlock;
-import net.minecraft.world.level.block.SupportType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
@@ -49,6 +46,8 @@ public class DefaultFluidRenderer {
private final MutableFloat scratchHeight = new MutableFloat(0);
private final MutableInt scratchSamples = new MutableInt();
+ private final BlockOcclusionCache occlusionCache = new BlockOcclusionCache();
+
private final ModelQuadViewMutable quad = new ModelQuad();
private final LightPipelineProvider lighters;
@@ -65,21 +64,10 @@ public DefaultFluidRenderer(LightPipelineProvider lighters) {
this.lighters = lighters;
}
- private boolean isFluidOccluded(BlockAndTintGetter world, int x, int y, int z, Direction dir, BlockState blockState, Fluid fluid) {
- //Test own block state first, this prevents waterlogged blocks from having hidden internal geometry
- // which can result in z-fighting
- var pos = this.scratchPos.set(x, y, z);
- if (blockState.canOcclude() && blockState.isFaceSturdy(world, pos, dir, SupportType.FULL)) {
- return true;
- }
-
- //Test neighboring block state
- var adjPos = this.scratchPos.set(x + dir.getStepX(), y + dir.getStepY(), z + dir.getStepZ());
- BlockState adjBlockState = world.getBlockState(adjPos);
- if (adjBlockState.getFluidState().getType().isSame(fluid)) {
- return true;
- }
- return adjBlockState.canOcclude() && dir != Direction.UP && adjBlockState.isFaceSturdy(world, adjPos, dir.getOpposite(), SupportType.FULL);
+ private boolean isFullBlockFluidOccluded(BlockAndTintGetter world, BlockPos pos, Direction dir, BlockState blockState, FluidState fluid) {
+ // check if this face of the fluid, assuming a full-block cull shape, is occluded by the block it's in or a neighboring block.
+ // it doesn't do a voxel shape comparison with the neighboring blocks since that is already done by isSideExposed
+ return !this.occlusionCache.shouldDrawFullBlockFluidSide(blockState, world, pos, dir, fluid, Shapes.block());
}
private boolean isSideExposed(BlockAndTintGetter world, int x, int y, int z, Direction dir, float height) {
@@ -87,7 +75,7 @@ private boolean isSideExposed(BlockAndTintGetter world, int x, int y, int z, Dir
BlockState blockState = world.getBlockState(pos);
if (blockState.canOcclude()) {
- VoxelShape shape = blockState.getOcclusionShape(world, pos);
+ VoxelShape shape = blockState.getOcclusionShape();
// Hoist these checks to avoid allocating the shape below
if (shape.isEmpty()) {
@@ -109,15 +97,16 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat
Fluid fluid = fluidState.getType();
- boolean sfUp = this.isFluidOccluded(level, posX, posY, posZ, Direction.UP, blockState, fluid);
- boolean sfDown = this.isFluidOccluded(level, posX, posY, posZ, Direction.DOWN, blockState, fluid) ||
+ boolean cullUp = this.isFullBlockFluidOccluded(level, blockPos, Direction.UP, blockState, fluidState);
+ boolean cullDown = this.isFullBlockFluidOccluded(level, blockPos, Direction.DOWN, blockState, fluidState) ||
!this.isSideExposed(level, posX, posY, posZ, Direction.DOWN, 0.8888889F);
- boolean sfNorth = this.isFluidOccluded(level, posX, posY, posZ, Direction.NORTH, blockState, fluid);
- boolean sfSouth = this.isFluidOccluded(level, posX, posY, posZ, Direction.SOUTH, blockState, fluid);
- boolean sfWest = this.isFluidOccluded(level, posX, posY, posZ, Direction.WEST, blockState, fluid);
- boolean sfEast = this.isFluidOccluded(level, posX, posY, posZ, Direction.EAST, blockState, fluid);
+ boolean cullNorth = this.isFullBlockFluidOccluded(level, blockPos, Direction.NORTH, blockState, fluidState);
+ boolean cullSouth = this.isFullBlockFluidOccluded(level, blockPos, Direction.SOUTH, blockState, fluidState);
+ boolean cullWest = this.isFullBlockFluidOccluded(level, blockPos, Direction.WEST, blockState, fluidState);
+ boolean cullEast = this.isFullBlockFluidOccluded(level, blockPos, Direction.EAST, blockState, fluidState);
- if (sfUp && sfDown && sfEast && sfWest && sfNorth && sfSouth) {
+ // stop rendering if all faces of the fluid are occluded
+ if (cullUp && cullDown && cullEast && cullWest && cullNorth && cullSouth) {
return;
}
@@ -149,7 +138,7 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat
.move(Direction.NORTH)
.move(Direction.EAST));
}
- float yOffset = sfDown ? 0.0F : EPSILON;
+ float yOffset = cullDown ? 0.0F : EPSILON;
final ModelQuadViewMutable quad = this.quad;
@@ -158,7 +147,7 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat
quad.setFlags(0);
- if (!sfUp && this.isSideExposed(level, posX, posY, posZ, Direction.UP, Math.min(Math.min(northWestHeight, southWestHeight), Math.min(southEastHeight, northEastHeight)))) {
+ if (!cullUp && this.isSideExposed(level, posX, posY, posZ, Direction.UP, Math.min(Math.min(northWestHeight, southWestHeight), Math.min(southEastHeight, northEastHeight)))) {
northWestHeight -= EPSILON;
southWestHeight -= EPSILON;
southEastHeight -= EPSILON;
@@ -243,7 +232,7 @@ && isAlignedEquals(southEastHeight, southWestHeight)
}
}
- if (!sfDown) {
+ if (!cullDown) {
TextureAtlasSprite sprite = sprites[0];
float minU = sprite.getU0();
@@ -273,7 +262,7 @@ && isAlignedEquals(southEastHeight, southWestHeight)
switch (dir) {
case NORTH -> {
- if (sfNorth) {
+ if (cullNorth) {
continue;
}
c1 = northWestHeight;
@@ -284,7 +273,7 @@ && isAlignedEquals(southEastHeight, southWestHeight)
z2 = z1;
}
case SOUTH -> {
- if (sfSouth) {
+ if (cullSouth) {
continue;
}
c1 = southEastHeight;
@@ -295,7 +284,7 @@ && isAlignedEquals(southEastHeight, southWestHeight)
z2 = z1;
}
case WEST -> {
- if (sfWest) {
+ if (cullWest) {
continue;
}
c1 = southWestHeight;
@@ -306,7 +295,7 @@ && isAlignedEquals(southEastHeight, southWestHeight)
z2 = 0.0f;
}
case EAST -> {
- if (sfEast) {
+ if (cullEast) {
continue;
}
c1 = northEastHeight;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java
index f3992c935a..a328e552c6 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java
@@ -3,6 +3,7 @@
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import net.caffeinemc.mods.sodium.client.SodiumClientMod;
import net.caffeinemc.mods.sodium.client.render.chunk.ExtendedBlockEntityType;
+import net.caffeinemc.mods.sodium.client.render.chunk.DefaultChunkRenderer;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext;
@@ -32,6 +33,8 @@
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
+import net.minecraft.util.profiling.Profiler;
+import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
@@ -57,6 +60,7 @@ public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc ab
@Override
public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToken cancellationToken) {
+ ProfilerFiller profiler = Profiler.get();
BuiltSectionInfo.Builder renderData = new BuiltSectionInfo.Builder();
VisGraph occluder = new VisGraph();
@@ -82,13 +86,14 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
TranslucentGeometryCollector collector;
if (SodiumClientMod.options().performance.getSortBehavior() != SortBehavior.OFF) {
- collector = new TranslucentGeometryCollector(render.getPosition());
+ collector = new TranslucentGeometryCollector(this.render.getPosition());
} else {
collector = null;
}
BlockRenderer blockRenderer = cache.getBlockRenderer();
blockRenderer.prepare(buffers, slice, collector);
+ profiler.push("render blocks");
try {
for (int y = minY; y < maxY; y++) {
if (cancellationToken.isCancelled()) {
@@ -130,7 +135,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
}
}
- if (blockState.isSolidRender(slice, blockPos)) {
+ if (blockState.isSolidRender()) {
occluder.setOpaque(blockPos);
}
}
@@ -143,8 +148,9 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
// Create a new crash report for other exceptions (e.g. thrown in getQuads)
throw fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while building chunk meshes"), slice, blockPos);
}
+ profiler.popPush("mesh appenders");
- PlatformLevelRenderHooks.INSTANCE.runChunkMeshAppenders(renderContext.getRenderers(), type -> buffers.get(DefaultMaterials.forRenderLayer(type)).asFallbackVertexConsumer(DefaultMaterials.forRenderLayer(type), collector),
+ PlatformLevelRenderHooks.INSTANCE.runChunkMeshAppenders(this.renderContext.getRenderers(), type -> buffers.get(DefaultMaterials.forRenderLayer(type)).asFallbackVertexConsumer(DefaultMaterials.forRenderLayer(type), collector),
slice);
blockRenderer.release();
@@ -155,11 +161,18 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
}
Map meshes = new Reference2ReferenceOpenHashMap<>();
+ var visibleSlices = DefaultChunkRenderer.getVisibleFaces(
+ (int) this.absoluteCameraPos.x(), (int) this.absoluteCameraPos.y(), (int) this.absoluteCameraPos.z(),
+ this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ());
+ profiler.popPush("meshing");
for (TerrainRenderPass pass : DefaultTerrainRenderPasses.ALL) {
- // consolidate all translucent geometry into UNASSIGNED so that it's rendered
- // all together if it needs to share an index buffer between the directions
- BuiltSectionMeshParts mesh = buffers.createMesh(pass, pass.isTranslucent() && sortType.needsDirectionMixing);
+ // if the translucent geometry needs to share an index buffer between the directions,
+ // consolidate all translucent geometry into UNASSIGNED
+ boolean translucentBehavior = collector != null && pass.isTranslucent();
+ boolean forceUnassigned = translucentBehavior && sortType.needsDirectionMixing;
+ boolean sliceReordering = !translucentBehavior || sortType.allowSliceReordering;
+ BuiltSectionMeshParts mesh = buffers.createMesh(pass, visibleSlices, forceUnassigned, sliceReordering);
if (mesh != null) {
meshes.put(pass, mesh);
@@ -170,11 +183,14 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
// cancellation opportunity right before translucent sorting
if (cancellationToken.isCancelled()) {
meshes.forEach((pass, mesh) -> mesh.getVertexData().free());
+ profiler.pop();
return null;
}
renderData.setOcclusionData(occluder.resolve());
+ profiler.popPush("translucency sorting");
+
boolean reuseUploadedData = false;
TranslucentData translucentData = null;
if (collector != null) {
@@ -185,16 +201,19 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
}
var output = new ChunkBuildOutput(this.render, this.submitTime, translucentData, renderData.build(), meshes);
+
if (collector != null) {
if (reuseUploadedData) {
output.markAsReusingUploadedData();
} else if (translucentData instanceof PresentTranslucentData present) {
var sorter = present.getSorter();
sorter.writeIndexBuffer(this, true);
- output.copyResultFrom(sorter);
+ output.setSorter(sorter);
}
}
+ profiler.pop();
+
return output;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java
index c4efd4ddc1..d3178ceb8e 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java
@@ -1,6 +1,8 @@
package net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.Sorter;
+import net.minecraft.util.profiling.Profiler;
+import net.minecraft.util.profiling.ProfilerFiller;
import org.joml.Vector3dc;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
@@ -23,7 +25,13 @@ public ChunkSortOutput execute(ChunkBuildContext context, CancellationToken canc
if (cancellationToken.isCancelled()) {
return null;
}
+
+ ProfilerFiller profiler = Profiler.get();
+ profiler.push("translucency sorting");
+
this.sorter.writeIndexBuffer(this, false);
+
+ profiler.pop();
return new ChunkSortOutput(this.render, this.submitTime, this.sorter);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java
index ec250afb86..21a1736347 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java
@@ -1,13 +1,14 @@
package net.caffeinemc.mods.sodium.client.render.chunk.data;
+import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing;
import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
public class BuiltSectionMeshParts {
- private final int[] vertexCounts;
+ private final int[] vertexSegments;
private final NativeBuffer buffer;
public BuiltSectionMeshParts(NativeBuffer buffer, int[] vertexCounts) {
- this.vertexCounts = vertexCounts;
+ this.vertexSegments = vertexCounts;
this.buffer = buffer;
}
@@ -15,7 +16,17 @@ public NativeBuffer getVertexData() {
return this.buffer;
}
- public int[] getVertexCounts() {
- return this.vertexCounts;
+ public int[] getVertexSegments() {
+ return this.vertexSegments;
+ }
+
+ public int[] computeVertexCounts() {
+ var vertexCounts = new int[ModelQuadFacing.COUNT];
+
+ for (int i = 0; i < this.vertexSegments.length; i += 2) {
+ vertexCounts[this.vertexSegments[i + 1]] = this.vertexSegments[i];
+ }
+
+ return vertexCounts;
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java
index 87f9998284..99fbe9e69a 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java
@@ -1,12 +1,18 @@
package net.caffeinemc.mods.sodium.client.render.chunk.data;
+import net.caffeinemc.mods.sodium.client.gl.arena.GlBufferArena;
import net.caffeinemc.mods.sodium.client.gl.arena.GlBufferSegment;
+import net.caffeinemc.mods.sodium.client.gl.arena.PendingUpload;
+import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing;
+import net.caffeinemc.mods.sodium.client.render.chunk.SharedQuadIndexBuffer;
import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
+import net.caffeinemc.mods.sodium.client.util.UInt32;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
+import java.util.stream.Stream;
/**
* The section render data storage stores the gl buffer segments of uploaded
@@ -16,20 +22,24 @@
* buffer segments is stored in a natively allocated piece of memory referenced
* by {@code pMeshDataArray} and accessed through
* {@link SectionRenderDataUnsafe}.
- *
+ *
* When the backing buffer (from the gl buffer arena) is resized, the storage
* object is notified and then it updates the changed offsets of the buffer
* segments. Since the index data's size and alignment directly corresponds to
* that of the vertex data except for the vertex/index scaling of two thirds,
* only an offset to the index data within the index data buffer arena is
* stored.
- *
+ *
* Index and vertex data storage can be managed separately since they may be
* updated independently of each other (in both directions).
*/
public class SectionRenderDataStorage {
private final @Nullable GlBufferSegment[] vertexAllocations;
- private final @Nullable GlBufferSegment @Nullable[] elementAllocations;
+ private final @Nullable GlBufferSegment @Nullable [] elementAllocations;
+ private @Nullable GlBufferSegment sharedIndexAllocation;
+ private int sharedIndexCapacity = 0;
+ private boolean needsSharedIndexUpdate = false;
+ private final int[] sharedIndexUsage = new int[RenderRegion.REGION_SIZE];
private final long pMeshDataArray;
@@ -45,8 +55,7 @@ public SectionRenderDataStorage(boolean storesIndices) {
this.pMeshDataArray = SectionRenderDataUnsafe.allocateHeap(RenderRegion.REGION_SIZE);
}
- public void setVertexData(int localSectionIndex,
- GlBufferSegment allocation, int[] vertexCounts) {
+ public void setVertexData(int localSectionIndex, GlBufferSegment allocation, int[] vertexSegments) {
GlBufferSegment prev = this.vertexAllocations[localSectionIndex];
if (prev != null) {
@@ -58,22 +67,25 @@ public void setVertexData(int localSectionIndex,
var pMeshData = this.getDataPointer(localSectionIndex);
int sliceMask = 0;
- int vertexOffset = allocation.getOffset();
+ long facingList = 0;
- for (int facingIndex = 0; facingIndex < ModelQuadFacing.COUNT; facingIndex++) {
- int vertexCount = vertexCounts[facingIndex];
+ for (int i = 0; i < ModelQuadFacing.COUNT; i++) {
+ var segmentIndex = i << 1;
- SectionRenderDataUnsafe.setVertexOffset(pMeshData, facingIndex, vertexOffset);
- SectionRenderDataUnsafe.setElementCount(pMeshData, facingIndex, (vertexCount >> 2) * 6);
+ int facing = vertexSegments[segmentIndex + 1];
+ facingList |= (long) facing << (i * 8);
+
+ long vertexCount = UInt32.upcast(vertexSegments[segmentIndex]);
+ SectionRenderDataUnsafe.setVertexCount(pMeshData, i, vertexCount);
if (vertexCount > 0) {
- sliceMask |= 1 << facingIndex;
+ sliceMask |= 1 << facing;
}
-
- vertexOffset += vertexCount;
}
+ SectionRenderDataUnsafe.setBaseVertex(pMeshData, allocation.getOffset());
SectionRenderDataUnsafe.setSliceMask(pMeshData, sliceMask);
+ SectionRenderDataUnsafe.setFacingList(pMeshData, facingList);
}
public void setIndexData(int localSectionIndex, GlBufferSegment allocation) {
@@ -91,8 +103,88 @@ public void setIndexData(int localSectionIndex, GlBufferSegment allocation) {
var pMeshData = this.getDataPointer(localSectionIndex);
- SectionRenderDataUnsafe.setBaseElement(pMeshData,
- allocation.getOffset() | SectionRenderDataUnsafe.BASE_ELEMENT_MSB);
+ SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, allocation.getOffset());
+ }
+
+ public void setSharedIndexUsage(int localSectionIndex, int newUsage) {
+ var previousUsage = this.sharedIndexUsage[localSectionIndex];
+ if (previousUsage == newUsage) {
+ return;
+ }
+
+ // mark for update if usage is down from max (may need to shrink buffer)
+ // or if usage increased beyond the max (need to grow buffer)
+ if (newUsage < previousUsage && previousUsage == this.sharedIndexCapacity ||
+ newUsage > this.sharedIndexCapacity ||
+ newUsage > 0 && this.sharedIndexAllocation == null) {
+ this.needsSharedIndexUpdate = true;
+ } else {
+ // just set the base element since no update is happening
+ var sharedBaseElement = this.sharedIndexAllocation.getOffset();
+ var pMeshData = this.getDataPointer(localSectionIndex);
+ SectionRenderDataUnsafe.setSharedBaseElement(pMeshData, sharedBaseElement);
+ }
+
+ this.sharedIndexUsage[localSectionIndex] = newUsage;
+ }
+
+ public boolean needsSharedIndexUpdate() {
+ return this.needsSharedIndexUpdate;
+ }
+
+ /**
+ * Updates the shared index data buffer to match the current usage.
+ *
+ * @param arena The buffer arena to allocate the new buffer from
+ * @return true if the arena resized itself
+ */
+ public boolean updateSharedIndexData(CommandList commandList, GlBufferArena arena) {
+ // assumes this.needsSharedIndexUpdate is true when this is called
+ this.needsSharedIndexUpdate = false;
+
+ // determine the new required capacity
+ int newCapacity = 0;
+ for (int i = 0; i < RenderRegion.REGION_SIZE; i++) {
+ newCapacity = Math.max(newCapacity, this.sharedIndexUsage[i]);
+ }
+ if (newCapacity == this.sharedIndexCapacity) {
+ return false;
+ }
+
+ this.sharedIndexCapacity = newCapacity;
+
+ // remove the existing allocation and exit if we don't need to create a new one
+ if (this.sharedIndexAllocation != null) {
+ this.sharedIndexAllocation.delete();
+ this.sharedIndexAllocation = null;
+ }
+ if (this.sharedIndexCapacity == 0) {
+ return false;
+ }
+
+ // add some base-level capacity to avoid resizing the buffer too often
+ if (this.sharedIndexCapacity < 128) {
+ this.sharedIndexCapacity += 32;
+ }
+
+ // create and upload a new shared index buffer
+ var buffer = SharedQuadIndexBuffer.createIndexBuffer(SharedQuadIndexBuffer.IndexType.INTEGER, this.sharedIndexCapacity);
+ var pendingUpload = new PendingUpload(buffer);
+ var bufferChanged = arena.upload(commandList, Stream.of(pendingUpload));
+ this.sharedIndexAllocation = pendingUpload.getResult();
+ buffer.free();
+
+ // only write the base elements now if we're not going to do so again later because of the buffer resize
+ if (!bufferChanged) {
+ var sharedBaseElement = this.sharedIndexAllocation.getOffset();
+ for (int i = 0; i < RenderRegion.REGION_SIZE; i++) {
+ if (this.sharedIndexUsage[i] > 0) {
+ SectionRenderDataUnsafe.setSharedBaseElement(this.getDataPointer(i), sharedBaseElement);
+ }
+ }
+ }
+
+ return bufferChanged;
}
public void removeData(int localSectionIndex) {
@@ -101,6 +193,8 @@ public void removeData(int localSectionIndex) {
if (this.elementAllocations != null) {
this.removeIndexData(localSectionIndex);
}
+
+ this.setSharedIndexUsage(localSectionIndex, 0);
}
public void removeVertexData(int localSectionIndex) {
@@ -124,7 +218,7 @@ private void removeVertexData(int localSectionIndex, boolean retainIndexData) {
SectionRenderDataUnsafe.clear(pMeshData);
if (retainIndexData) {
- SectionRenderDataUnsafe.setBaseElement(pMeshData, baseElement);
+ SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, baseElement);
}
}
@@ -157,28 +251,27 @@ private void updateMeshes(int sectionIndex) {
return;
}
- var offset = allocation.getOffset();
var data = this.getDataPointer(sectionIndex);
-
- for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) {
- SectionRenderDataUnsafe.setVertexOffset(data, facing, offset);
-
- var count = SectionRenderDataUnsafe.getElementCount(data, facing);
- offset += (count / 6) * 4; // convert elements back into vertices
- }
+ long offset = allocation.getOffset();
+ SectionRenderDataUnsafe.setBaseVertex(data, offset);
}
public void onIndexBufferResized() {
- if (this.elementAllocations == null) {
- return;
+ long sharedBaseElement = 0;
+ if (this.sharedIndexAllocation != null) {
+ sharedBaseElement = this.sharedIndexAllocation.getOffset();
}
- for (int sectionIndex = 0; sectionIndex < RenderRegion.REGION_SIZE; sectionIndex++) {
- var allocation = this.elementAllocations[sectionIndex];
+ for (int i = 0; i < RenderRegion.REGION_SIZE; i++) {
+ if (this.sharedIndexUsage[i] > 0) {
+ // update index sharing sections to use the new shared index buffer's offset
+ SectionRenderDataUnsafe.setSharedBaseElement(this.getDataPointer(i), sharedBaseElement);
+ } else if (this.elementAllocations != null) {
+ var allocation = this.elementAllocations[i];
- if (allocation != null) {
- SectionRenderDataUnsafe.setBaseElement(this.getDataPointer(sectionIndex),
- allocation.getOffset() | SectionRenderDataUnsafe.BASE_ELEMENT_MSB);
+ if (allocation != null) {
+ SectionRenderDataUnsafe.setLocalBaseElement(this.getDataPointer(i), allocation.getOffset());
+ }
}
}
}
@@ -194,6 +287,10 @@ public void delete() {
deleteAllocations(this.elementAllocations);
}
+ if (this.sharedIndexAllocation != null) {
+ this.sharedIndexAllocation.delete();
+ }
+
SectionRenderDataUnsafe.freeHeap(this.pMeshDataArray);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java
index cf483e1227..0db9cfb209 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java
@@ -1,5 +1,6 @@
package net.caffeinemc.mods.sodium.client.render.chunk.data;
+import net.caffeinemc.mods.sodium.client.util.UInt32;
import org.lwjgl.system.MemoryUtil;
// This code is a terrible hack to get around the fact that we are so incredibly memory bound, and that we
@@ -15,20 +16,16 @@
// Please never try to write performance critical code in Java. This is what it will do to you. And you will still be
// three times slower than the most naive solution in literally any other language that LLVM can compile.
-// struct SectionRenderData { // 64 bytes
-// base_element: u32
-// mask: u32,
-// ranges: [VertexRange; 7]
-// }
-//
-// struct VertexRange { // 8 bytes
-// offset: u32,
-// count: u32
+// struct SectionRenderData { // 48 bytes
+// base_element: u32,
+// base_vertex: u32,
+// is_local_index: u8,
+// facing_list: u56,
+// slice_mask: u32,
+// vertex_count: [u32; 7]
// }
public class SectionRenderDataUnsafe {
- public static final int BASE_ELEMENT_MSB = 1 << 31;
-
/**
* When the "base element" field is not specified (indicated by setting the MSB to 0), the indices for the geometry set
* should be sourced from a monotonic sequence (see {@link net.caffeinemc.mods.sodium.client.render.chunk.SharedQuadIndexBuffer}).
@@ -36,9 +33,11 @@ public class SectionRenderDataUnsafe {
* Otherwise, indices should be sourced from the index buffer for the render region using the specified offset.
*/
private static final long OFFSET_BASE_ELEMENT = 0;
-
- private static final long OFFSET_SLICE_MASK = 4;
- private static final long OFFSET_SLICE_RANGES = 8;
+ private static final long OFFSET_BASE_VERTEX = 4;
+ private static final long OFFSET_FACING_LIST = 8;
+ private static final long OFFSET_IS_LOCAL_INDEX = 15;
+ private static final long OFFSET_SLICE_MASK = 16;
+ private static final long OFFSET_ELEMENT_COUNTS = 20;
private static final long ALIGNMENT = 64;
private static final long STRIDE = 64; // cache-line friendly! :)
@@ -64,6 +63,20 @@ public static long heapPointer(long ptr, int index) {
return ptr + (index * STRIDE);
}
+ public static void setLocalBaseElement(long ptr, long value /* Uint32 */) {
+ MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value));
+ MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, (byte) 1);
+ }
+
+ public static void setSharedBaseElement(long ptr, long value /* Uint32 */) {
+ MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value));
+ MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, (byte) 0);
+ }
+
+ public static long getBaseElement(long ptr) {
+ return Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + OFFSET_BASE_ELEMENT));
+ }
+
public static void setSliceMask(long ptr, int value) {
MemoryUtil.memPutInt(ptr + OFFSET_SLICE_MASK, value);
}
@@ -72,27 +85,31 @@ public static int getSliceMask(long ptr) {
return MemoryUtil.memGetInt(ptr + OFFSET_SLICE_MASK);
}
- public static void setBaseElement(long ptr, int value) {
- MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, value);
+ public static void setFacingList(long ptr, long facingList) {
+ MemoryUtil.memPutLong(ptr + OFFSET_FACING_LIST, facingList);
+ }
+
+ public static long getFacingList(long ptr) {
+ return MemoryUtil.memGetLong(ptr + OFFSET_FACING_LIST);
}
- public static int getBaseElement(long ptr) {
- return MemoryUtil.memGetInt(ptr + OFFSET_BASE_ELEMENT);
+ public static boolean isLocalIndex(long ptr) {
+ return MemoryUtil.memGetByte(ptr + OFFSET_IS_LOCAL_INDEX) != 0;
}
- public static void setVertexOffset(long ptr, int facing, int value) {
- MemoryUtil.memPutInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 0L, value);
+ public static void setBaseVertex(long ptr, long value /* Uint32 */) {
+ MemoryUtil.memPutInt(ptr + OFFSET_BASE_VERTEX, UInt32.downcast(value));
}
- public static int getVertexOffset(long ptr, int facing) {
- return MemoryUtil.memGetInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 0L);
+ public static long /* Uint32 */ getBaseVertex(long ptr) {
+ return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_BASE_VERTEX));
}
- public static void setElementCount(long ptr, int facing, int value) {
- MemoryUtil.memPutInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 4L, value);
+ public static void setVertexCount(long ptr, int index, long count /* Uint32 */) {
+ MemoryUtil.memPutInt(ptr + OFFSET_ELEMENT_COUNTS + (index * 4), UInt32.downcast(count));
}
- public static int getElementCount(long ptr, int facing) {
- return MemoryUtil.memGetInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 4L);
+ public static long /* Uint32 */ getVertexCount(long ptr, int index) {
+ return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_ELEMENT_COUNTS + (index * 4)));
}
}
\ No newline at end of file
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java
index 75e6757df8..e45638b8ad 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java
@@ -1,11 +1,14 @@
package net.caffeinemc.mods.sodium.client.render.chunk.lists;
+import net.caffeinemc.mods.sodium.client.render.chunk.LocalSectionIndex;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags;
+import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
+import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator;
import net.caffeinemc.mods.sodium.client.util.iterator.ByteIterator;
import net.caffeinemc.mods.sodium.client.util.iterator.ReversibleByteArrayIterator;
-import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator;
-import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
+import net.minecraft.core.SectionPos;
+import net.minecraft.util.Mth;
import org.jetbrains.annotations.Nullable;
public class ChunkRenderList {
@@ -37,6 +40,39 @@ public void reset(int frame) {
this.lastVisibleFrame = frame;
}
+ // clamping the relative camera position to the region bounds means there can only be very few different distances
+ private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2;
+
+ public void sortSections(SectionPos cameraPos, int[] sortItems) {
+ var cameraX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1);
+ var cameraY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1);
+ var cameraZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), 0, RenderRegion.REGION_LENGTH - 1);
+
+ int[] histogram = new int[SORTING_HISTOGRAM_SIZE];
+
+ for (int i = 0; i < this.sectionsWithGeometryCount; i++) {
+ var index = this.sectionsWithGeometry[i] & 0xFF; // makes sure the byte -> int conversion is unsigned
+ var x = Math.abs(LocalSectionIndex.unpackX(index) - cameraX);
+ var y = Math.abs(LocalSectionIndex.unpackY(index) - cameraY);
+ var z = Math.abs(LocalSectionIndex.unpackZ(index) - cameraZ);
+
+ var distance = x + y + z;
+ histogram[distance]++;
+ sortItems[i] = distance << 8 | index;
+ }
+
+ // prefix sum to calculate indexes
+ for (int i = 1; i < SORTING_HISTOGRAM_SIZE; i++) {
+ histogram[i] += histogram[i - 1];
+ }
+
+ for (int i = 0; i < this.sectionsWithGeometryCount; i++) {
+ var item = sortItems[i];
+ var distance = item >>> 8;
+ this.sectionsWithGeometry[--histogram[distance]] = (byte) item;
+ }
+ }
+
public void add(RenderSection render) {
if (this.size >= RenderRegion.REGION_SIZE) {
throw new ArrayIndexOutOfBoundsException("Render list is full");
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java
index 1837e0ae9d..080530bf11 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java
@@ -1,9 +1,7 @@
package net.caffeinemc.mods.sodium.client.render.chunk.lists;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.util.iterator.ReversibleObjectArrayIterator;
-import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
/**
* Stores one render list of sections per region, sorted by the order in which
@@ -27,44 +25,4 @@ public ReversibleObjectArrayIterator iterator(boolean reverse)
public static SortedRenderLists empty() {
return EMPTY;
}
-
- public static class Builder {
- private final ObjectArrayList lists = new ObjectArrayList<>();
- private final int frame;
-
- public Builder(int frame) {
- this.frame = frame;
- }
-
- public void add(RenderSection section) {
- RenderRegion region = section.getRegion();
- ChunkRenderList list = region.getRenderList();
-
- // Even if a section does not have render objects, we must ensure the render list is initialized and put
- // into the sorted queue of lists, so that we maintain the correct order of draw calls.
- if (list.getLastVisibleFrame() != this.frame) {
- list.reset(this.frame);
-
- this.lists.add(list);
- }
-
- // Only add the section to the render list if it actually contains render objects
- if (section.getFlags() != 0) {
- list.add(section);
- }
- }
-
- public SortedRenderLists build() {
- var filtered = new ObjectArrayList(this.lists.size());
-
- // Filter any empty render lists
- for (var list : this.lists) {
- if (list.size() > 0) {
- filtered.add(list);
- }
- }
-
- return new SortedRenderLists(filtered);
- }
- }
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java
index 5ed657795f..89cf22cf78 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java
@@ -1,12 +1,17 @@
package net.caffeinemc.mods.sodium.client.render.chunk.lists;
+import it.unimi.dsi.fastutil.ints.IntArrays;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateType;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller;
import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
+import net.caffeinemc.mods.sodium.client.render.viewport.Viewport;
-import java.util.*;
+import java.util.ArrayDeque;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Queue;
/**
* The visible chunk collector is passed to the occlusion graph search culler to
@@ -30,22 +35,22 @@ public VisibleChunkCollector(int frame) {
}
@Override
- public void visit(RenderSection section, boolean visible) {
- RenderRegion region = section.getRegion();
- ChunkRenderList renderList = region.getRenderList();
+ public void visit(RenderSection section) {
+ // only process section (and associated render list) if it has content that needs rendering
+ if (section.getFlags() != 0) {
+ RenderRegion region = section.getRegion();
+ ChunkRenderList renderList = region.getRenderList();
- // Even if a section does not have render objects, we must ensure the render list is initialized and put
- // into the sorted queue of lists, so that we maintain the correct order of draw calls.
- if (renderList.getLastVisibleFrame() != this.frame) {
- renderList.reset(this.frame);
+ if (renderList.getLastVisibleFrame() != this.frame) {
+ renderList.reset(this.frame);
- this.sortedRenderLists.add(renderList);
- }
+ this.sortedRenderLists.add(renderList);
+ }
- if (visible && section.getFlags() != 0) {
renderList.add(section);
}
+ // always add to rebuild lists though, because it might just not be built yet
this.addToRebuildLists(section);
}
@@ -61,8 +66,42 @@ private void addToRebuildLists(RenderSection section) {
}
}
- public SortedRenderLists createRenderLists() {
- return new SortedRenderLists(this.sortedRenderLists);
+ private static int[] sortItems = new int[RenderRegion.REGION_SIZE];
+
+ public SortedRenderLists createRenderLists(Viewport viewport) {
+ // sort the regions by distance to fix rare region ordering bugs
+ var sectionPos = viewport.getChunkCoord();
+ var cameraX = sectionPos.getX() >> RenderRegion.REGION_WIDTH_SH;
+ var cameraY = sectionPos.getY() >> RenderRegion.REGION_HEIGHT_SH;
+ var cameraZ = sectionPos.getZ() >> RenderRegion.REGION_LENGTH_SH;
+ var size = this.sortedRenderLists.size();
+
+ if (sortItems.length < size) {
+ sortItems = new int[size];
+ }
+
+ for (var i = 0; i < size; i++) {
+ var region = this.sortedRenderLists.get(i).getRegion();
+ var x = Math.abs(region.getX() - cameraX);
+ var y = Math.abs(region.getY() - cameraY);
+ var z = Math.abs(region.getZ() - cameraZ);
+ sortItems[i] = (x + y + z) << 16 | i;
+ }
+
+ IntArrays.unstableSort(sortItems, 0, size);
+
+ var sorted = new ObjectArrayList(size);
+ for (var i = 0; i < size; i++) {
+ var key = sortItems[i];
+ var renderList = this.sortedRenderLists.get(key & 0xFFFF);
+ sorted.add(renderList);
+ }
+
+ for (var list : sorted) {
+ list.sortSections(sectionPos, sortItems);
+ }
+
+ return new SortedRenderLists(sorted);
}
public Map> getRebuildLists() {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java
index 3bd0376297..a6550ff8ca 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java
@@ -37,6 +37,8 @@ public void findVisible(Visitor visitor,
while (queues.flip()) {
processQueue(visitor, viewport, searchDistance, useOcclusionCulling, frame, queues.read(), queues.write());
}
+
+ this.addNearbySections(visitor, viewport, searchDistance, frame);
}
private static void processQueue(Visitor visitor,
@@ -50,21 +52,26 @@ private static void processQueue(Visitor visitor,
RenderSection section;
while ((section = readQueue.dequeue()) != null) {
- boolean visible = isSectionVisible(section, viewport, searchDistance);
- visitor.visit(section, visible);
-
- if (!visible) {
+ if (!isSectionVisible(section, viewport, searchDistance)) {
continue;
}
+ visitor.visit(section);
+
int connections;
{
if (useOcclusionCulling) {
+ var sectionVisibilityData = section.getVisibilityData();
+
+ // occlude paths through the section if it's being viewed at an angle where
+ // the other side can't possibly be seen
+ sectionVisibilityData &= getAngleVisibilityMask(viewport, section);
+
// When using occlusion culling, we can only traverse into neighbors for which there is a path of
// visibility through this chunk. This is determined by taking all the incoming paths to this chunk and
// creating a union of the outgoing paths from those.
- connections = VisibilityEncoding.getConnections(section.getVisibilityData(), section.getIncomingDirections());
+ connections = VisibilityEncoding.getConnections(sectionVisibilityData, section.getIncomingDirections());
} else {
// Not using any occlusion culling, so traversing in any direction is legal.
connections = GraphDirectionSet.ALL;
@@ -79,6 +86,30 @@ private static void processQueue(Visitor visitor,
}
}
+ private static final long UP_DOWN_OCCLUDED = (1L << VisibilityEncoding.bit(GraphDirection.DOWN, GraphDirection.UP)) | (1L << VisibilityEncoding.bit(GraphDirection.UP, GraphDirection.DOWN));
+ private static final long NORTH_SOUTH_OCCLUDED = (1L << VisibilityEncoding.bit(GraphDirection.NORTH, GraphDirection.SOUTH)) | (1L << VisibilityEncoding.bit(GraphDirection.SOUTH, GraphDirection.NORTH));
+ private static final long WEST_EAST_OCCLUDED = (1L << VisibilityEncoding.bit(GraphDirection.WEST, GraphDirection.EAST)) | (1L << VisibilityEncoding.bit(GraphDirection.EAST, GraphDirection.WEST));
+
+ private static long getAngleVisibilityMask(Viewport viewport, RenderSection section) {
+ var transform = viewport.getTransform();
+ var dx = Math.abs(transform.x - section.getCenterX());
+ var dy = Math.abs(transform.y - section.getCenterY());
+ var dz = Math.abs(transform.z - section.getCenterZ());
+
+ var angleOcclusionMask = 0L;
+ if (dx > dy || dz > dy) {
+ angleOcclusionMask |= UP_DOWN_OCCLUDED;
+ }
+ if (dx > dz || dy > dz) {
+ angleOcclusionMask |= NORTH_SOUTH_OCCLUDED;
+ }
+ if (dy > dx || dz > dx) {
+ angleOcclusionMask |= WEST_EAST_OCCLUDED;
+ }
+
+ return ~angleOcclusionMask;
+ }
+
private static boolean isSectionVisible(RenderSection section, Viewport viewport, float maxDistance) {
return isWithinRenderDistance(viewport.getTransform(), section, maxDistance) && isWithinFrustum(viewport, section);
}
@@ -179,13 +210,54 @@ private static int nearestToZero(int min, int max) {
// The bounding box of a chunk section must be large enough to contain all possible geometry within it. Block models
// can extend outside a block volume by +/- 1.0 blocks on all axis. Additionally, we make use of a small epsilon
// to deal with floating point imprecision during a frustum check (see GH#2132).
- private static final float CHUNK_SECTION_SIZE = 8.0f /* chunk bounds */ + 1.0f /* maximum model extent */ + 0.125f /* epsilon */;
+ private static final float CHUNK_SECTION_RADIUS = 8.0f /* chunk bounds */;
+ private static final float CHUNK_SECTION_SIZE = CHUNK_SECTION_RADIUS + 1.0f /* maximum model extent */ + 0.125f /* epsilon */;
public static boolean isWithinFrustum(Viewport viewport, RenderSection section) {
return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(),
CHUNK_SECTION_SIZE, CHUNK_SECTION_SIZE, CHUNK_SECTION_SIZE);
}
+ // this bigger chunk section size is only used for frustum-testing nearby sections with large models
+ private static final float CHUNK_SECTION_SIZE_NEARBY = CHUNK_SECTION_RADIUS + 2.0f /* bigger model extent */ + 0.125f /* epsilon */;
+
+ public static boolean isWithinNearbySectionFrustum(Viewport viewport, RenderSection section) {
+ return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(),
+ CHUNK_SECTION_SIZE_NEARBY, CHUNK_SECTION_SIZE_NEARBY, CHUNK_SECTION_SIZE_NEARBY);
+ }
+
+ // This method visits sections near the origin that are not in the path of the graph traversal
+ // but have bounding boxes that may intersect with the frustum. It does this additional check
+ // for all neighboring, even diagonally neighboring, sections around the origin to render them
+ // if their extended bounding box is visible, and they may render large models that extend
+ // outside the 16x16x16 base volume of the section.
+ private void addNearbySections(Visitor visitor, Viewport viewport, float searchDistance, int frame) {
+ var origin = viewport.getChunkCoord();
+ var originX = origin.getX();
+ var originY = origin.getY();
+ var originZ = origin.getZ();
+
+ for (var dx = -1; dx <= 1; dx++) {
+ for (var dy = -1; dy <= 1; dy++) {
+ for (var dz = -1; dz <= 1; dz++) {
+ if (dx == 0 && dy == 0 && dz == 0) {
+ continue;
+ }
+
+ var section = this.getRenderSection(originX + dx, originY + dy, originZ + dz);
+
+ // additionally render not yet visited but visible sections
+ if (section != null && section.getLastVisibleFrame() != frame && isWithinNearbySectionFrustum(viewport, section)) {
+ // reset state on first visit, but don't enqueue
+ section.setLastVisibleFrame(frame);
+
+ visitor.visit(section);
+ }
+ }
+ }
+ }
+ }
+
private void init(Visitor visitor,
WriteQueue queue,
Viewport viewport,
@@ -195,14 +267,14 @@ private void init(Visitor visitor,
{
var origin = viewport.getChunkCoord();
- if (origin.getY() < this.level.getMinSection()) {
+ if (origin.getY() < this.level.getMinSectionY()) {
// below the level
this.initOutsideWorldHeight(queue, viewport, searchDistance, frame,
- this.level.getMinSection(), GraphDirection.DOWN);
- } else if (origin.getY() >= this.level.getMaxSection()) {
+ this.level.getMinSectionY(), GraphDirection.DOWN);
+ } else if (origin.getY() > this.level.getMaxSectionY()) {
// above the level
this.initOutsideWorldHeight(queue, viewport, searchDistance, frame,
- this.level.getMaxSection() - 1, GraphDirection.UP);
+ this.level.getMaxSectionY(), GraphDirection.UP);
} else {
this.initWithinWorld(visitor, queue, viewport, useOcclusionCulling, frame);
}
@@ -219,7 +291,7 @@ private void initWithinWorld(Visitor visitor, WriteQueue queue, V
section.setLastVisibleFrame(frame);
section.setIncomingDirections(GraphDirectionSet.NONE);
- visitor.visit(section, true);
+ visitor.visit(section);
int outgoing;
@@ -305,6 +377,6 @@ private RenderSection getRenderSection(int x, int y, int z) {
}
public interface Visitor {
- void visit(RenderSection section, boolean visible);
+ void visit(RenderSection section);
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/VisibilityEncoding.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/VisibilityEncoding.java
index db7e9e88f7..cf184efde0 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/VisibilityEncoding.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/VisibilityEncoding.java
@@ -21,7 +21,7 @@ public static long encode(@NotNull VisibilitySet occlusionData) {
return visibilityData;
}
- private static int bit(int from, int to) {
+ public static int bit(int from, int to) {
return (from * 8) + to;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java
index df6df676d4..e4dc883669 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java
@@ -24,13 +24,13 @@ public class RenderRegion {
public static final int REGION_HEIGHT = 4;
public static final int REGION_LENGTH = 8;
- private static final int REGION_WIDTH_M = RenderRegion.REGION_WIDTH - 1;
- private static final int REGION_HEIGHT_M = RenderRegion.REGION_HEIGHT - 1;
- private static final int REGION_LENGTH_M = RenderRegion.REGION_LENGTH - 1;
+ public static final int REGION_WIDTH_M = RenderRegion.REGION_WIDTH - 1;
+ public static final int REGION_HEIGHT_M = RenderRegion.REGION_HEIGHT - 1;
+ public static final int REGION_LENGTH_M = RenderRegion.REGION_LENGTH - 1;
- protected static final int REGION_WIDTH_SH = Integer.bitCount(REGION_WIDTH_M);
- protected static final int REGION_HEIGHT_SH = Integer.bitCount(REGION_HEIGHT_M);
- protected static final int REGION_LENGTH_SH = Integer.bitCount(REGION_LENGTH_M);
+ public static final int REGION_WIDTH_SH = Integer.bitCount(REGION_WIDTH_M);
+ public static final int REGION_HEIGHT_SH = Integer.bitCount(REGION_HEIGHT_M);
+ public static final int REGION_LENGTH_SH = Integer.bitCount(REGION_LENGTH_M);
public static final int REGION_SIZE = REGION_WIDTH * REGION_HEIGHT * REGION_LENGTH;
@@ -64,6 +64,18 @@ public static long key(int x, int y, int z) {
return SectionPos.asLong(x, y, z);
}
+ public int getX() {
+ return this.x;
+ }
+
+ public int getY() {
+ return this.y;
+ }
+
+ public int getZ() {
+ return this.z;
+ }
+
public int getChunkX() {
return this.x << REGION_WIDTH_SH;
}
@@ -217,9 +229,9 @@ public DeviceResources(CommandList commandList, StagingBuffer stagingBuffer) {
// the magic number 756 for the initial size is arbitrary, it was made up.
var initialVertices = 756;
- this.geometryArena = new GlBufferArena(commandList, REGION_SIZE * initialVertices, stride, stagingBuffer);
+ this.geometryArena = new GlBufferArena(commandList, REGION_SIZE * initialVertices, stride, stagingBuffer, false);
var initialIndices = (initialVertices / 4) * 6;
- this.indexArena = new GlBufferArena(commandList, REGION_SIZE * initialIndices, Integer.BYTES, stagingBuffer);
+ this.indexArena = new GlBufferArena(commandList, REGION_SIZE * initialIndices, Integer.BYTES, stagingBuffer, true);
}
public void updateTessellation(CommandList commandList, GlTessellation tessellation) {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java
index 71665e93a2..cdd6df0d72 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java
@@ -18,6 +18,9 @@
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
+import net.minecraft.util.profiling.Profiler;
+import net.minecraft.util.profiling.ProfilerFiller;
+import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.SharedIndexSorter;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@@ -80,35 +83,51 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect
if (mesh != null) {
uploads.add(new PendingSectionMeshUpload(result.render, mesh, pass,
- new PendingUpload(mesh.getVertexData())));
+ new PendingUpload(mesh.getVertexData())));
}
}
}
if (result instanceof ChunkSortOutput indexDataOutput && !indexDataOutput.isReusingUploadedIndexData()) {
- var buffer = indexDataOutput.getIndexBuffer();
-
- // when a non-present TranslucentData is used like NoData, the indexBuffer is null
- if (buffer == null) {
- continue;
- }
+ var sorter = indexDataOutput.getSorter();
+ if (sorter instanceof SharedIndexSorter sharedIndexSorter) {
+ var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT);
+ storage.removeIndexData(renderSectionIndex);
+ storage.setSharedIndexUsage(renderSectionIndex, sharedIndexSorter.quadCount());
+ } else {
+ var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT);
+ if (storage != null) {
+ storage.removeIndexData(renderSectionIndex);
+ storage.setSharedIndexUsage(renderSectionIndex, 0);
+ }
- indexUploads.add(new PendingSectionIndexBufferUpload(result.render, new PendingUpload(buffer)));
+ if (sorter == null) {
+ continue;
+ }
+ // when a non-present TranslucentData is used like NoData, the indexBuffer is null
+ var buffer = sorter.getIndexBuffer();
+ if (buffer == null) {
+ continue;
+ }
- var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT);
- if (storage != null) {
- storage.removeIndexData(renderSectionIndex);
+ indexUploads.add(new PendingSectionIndexBufferUpload(result.render, new PendingUpload(buffer)));
}
}
}
+ ProfilerFiller profiler = Profiler.get();
+
// If we have nothing to upload, abort!
- if (uploads.isEmpty() && indexUploads.isEmpty()) {
+ var translucentStorage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT);
+ var needsSharedIndexUpdate = translucentStorage != null && translucentStorage.needsSharedIndexUpdate();
+ if (uploads.isEmpty() && indexUploads.isEmpty() && !needsSharedIndexUpdate) {
return;
}
var resources = region.createResources(commandList);
+ profiler.push("upload_vertices");
+
if (!uploads.isEmpty()) {
var arena = resources.getGeometryArena();
boolean bufferChanged = arena.upload(commandList, uploads.stream()
@@ -124,24 +143,33 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect
for (PendingSectionMeshUpload upload : uploads) {
var storage = region.createStorage(upload.pass);
storage.setVertexData(upload.section.getSectionIndex(),
- upload.vertexUpload.getResult(), upload.meshData.getVertexCounts());
+ upload.vertexUpload.getResult(), upload.meshData.getVertexSegments());
}
}
+ profiler.popPush("upload_indices");
+ var indexBufferChanged = false;
+
if (!indexUploads.isEmpty()) {
var arena = resources.getIndexArena();
- boolean bufferChanged = arena.upload(commandList, indexUploads.stream()
+ indexBufferChanged = arena.upload(commandList, indexUploads.stream()
.map(upload -> upload.indexBufferUpload));
- if (bufferChanged) {
- region.refreshIndexedTesselation(commandList);
- }
-
for (PendingSectionIndexBufferUpload upload : indexUploads) {
var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT);
storage.setIndexData(upload.section.getSectionIndex(), upload.indexBufferUpload.getResult());
}
}
+
+ if (needsSharedIndexUpdate) {
+ indexBufferChanged |= translucentStorage.updateSharedIndexData(commandList, resources.getIndexArena());
+ }
+
+ if (indexBufferChanged) {
+ region.refreshIndexedTesselation(commandList);
+ }
+
+ profiler.pop();
}
private Reference2ReferenceMap.FastEntrySet> createMeshUploadQueues(Collection results) {
@@ -196,7 +224,6 @@ private record PendingSectionMeshUpload(RenderSection section, BuiltSectionMeshP
private record PendingSectionIndexBufferUpload(RenderSection section, PendingUpload indexBufferUpload) {
}
-
private static StagingBuffer createStagingBuffer(CommandList commandList) {
if (SodiumClientMod.options().advanced.useAdvancedStagingBuffers && MappedStagingBuffer.isSupported(RenderDevice.INSTANCE)) {
return new MappedStagingBuffer(commandList);
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderBindingPoints.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderBindingPoints.java
index b113ad0703..163190d51f 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderBindingPoints.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderBindingPoints.java
@@ -1,11 +1,10 @@
package net.caffeinemc.mods.sodium.client.render.chunk.shader;
public class ChunkShaderBindingPoints {
- public static final int ATTRIBUTE_POSITION_HI = 0;
- public static final int ATTRIBUTE_POSITION_LO = 1;
- public static final int ATTRIBUTE_COLOR = 2;
- public static final int ATTRIBUTE_TEXTURE = 3;
- public static final int ATTRIBUTE_LIGHT_MATERIAL_INDEX = 4;
+ public static final int ATTRIBUTE_POSITION = 0;
+ public static final int ATTRIBUTE_COLOR = 1;
+ public static final int ATTRIBUTE_TEXTURE = 2;
+ public static final int ATTRIBUTE_LIGHT_MATERIAL_INDEX = 3;
public static final int FRAG_COLOR = 0;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java
index 6855970186..8196d88c74 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java
@@ -45,11 +45,11 @@ public Smooth(ShaderBindingContext context) {
@Override
public void setup() {
- this.uFogColor.set(RenderSystem.getShaderFogColor());
- this.uFogShape.set(RenderSystem.getShaderFogShape().getIndex());
+ this.uFogColor.set(RenderSystem.getShaderFog().red(), RenderSystem.getShaderFog().green(), RenderSystem.getShaderFog().blue(), RenderSystem.getShaderFog().alpha());
+ this.uFogShape.set(RenderSystem.getShaderFog().shape().getIndex());
- this.uFogStart.setFloat(RenderSystem.getShaderFogStart());
- this.uFogEnd.setFloat(RenderSystem.getShaderFogEnd());
+ this.uFogStart.setFloat(RenderSystem.getShaderFog().start());
+ this.uFogEnd.setFloat(RenderSystem.getShaderFog().end());
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java
index d00713259a..2980f99365 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java
@@ -8,17 +8,17 @@ public enum SortType {
/**
* The section is fully empty, no index buffer is needed.
*/
- EMPTY_SECTION(false),
+ EMPTY_SECTION(false, true),
/**
* The section has no translucent geometry, no index buffer is needed.
*/
- NO_TRANSLUCENT(false),
+ NO_TRANSLUCENT(false, true),
/**
* No sorting is required and the sort order doesn't matter.
*/
- NONE(false),
+ NONE(false, true),
/**
* There is only one sort order. No active sorting is required, but an initial
@@ -45,8 +45,15 @@ public enum SortType {
DYNAMIC(true);
public final boolean needsDirectionMixing;
+ public final boolean allowSliceReordering;
SortType(boolean needsDirectionMixing) {
this.needsDirectionMixing = needsDirectionMixing;
+ this.allowSliceReordering = false;
+ }
+
+ SortType(boolean needsDirectionMixing, boolean allowSliceReordering) {
+ this.needsDirectionMixing = needsDirectionMixing;
+ this.allowSliceReordering = allowSliceReordering;
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TQuad.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TQuad.java
index bdf230a6ac..8d0ae60c73 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TQuad.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TQuad.java
@@ -25,37 +25,42 @@ public class TQuad {
private ModelQuadFacing facing;
private final float[] extents;
+ private float[] vertexPositions;
private final int packedNormal;
- private float dotProduct;
+ private final float accurateDotProduct;
+ private float quantizedDotProduct;
private Vector3fc center; // null on aligned quads
private Vector3fc quantizedNormal;
+ private Vector3fc accurateNormal;
- private TQuad(ModelQuadFacing facing, float[] extents, Vector3fc center, int packedNormal) {
+ private TQuad(ModelQuadFacing facing, float[] extents, float[] vertexPositions, Vector3fc center, int packedNormal) {
this.facing = facing;
this.extents = extents;
+ this.vertexPositions = vertexPositions;
this.center = center;
this.packedNormal = packedNormal;
if (this.facing.isAligned()) {
- this.dotProduct = getAlignedDotProduct(this.facing, this.extents);
+ this.accurateDotProduct = getAlignedDotProduct(this.facing, this.extents);
} else {
float normX = NormI8.unpackX(this.packedNormal);
float normY = NormI8.unpackY(this.packedNormal);
float normZ = NormI8.unpackZ(this.packedNormal);
- this.dotProduct = this.getCenter().dot(normX, normY, normZ);
+ this.accurateDotProduct = this.getCenter().dot(normX, normY, normZ);
}
+ this.quantizedDotProduct = this.accurateDotProduct;
}
private static float getAlignedDotProduct(ModelQuadFacing facing, float[] extents) {
return extents[facing.ordinal()] * facing.getSign();
}
- static TQuad fromAligned(ModelQuadFacing facing, float[] extents, Vector3fc center) {
- return new TQuad(facing, extents, center, ModelQuadFacing.PACKED_ALIGNED_NORMALS[facing.ordinal()]);
+ static TQuad fromAligned(ModelQuadFacing facing, float[] extents, float[] vertexPositions, Vector3fc center) {
+ return new TQuad(facing, extents, vertexPositions, center, ModelQuadFacing.PACKED_ALIGNED_NORMALS[facing.ordinal()]);
}
- static TQuad fromUnaligned(ModelQuadFacing facing, float[] extents, Vector3fc center, int packedNormal) {
- return new TQuad(facing, extents, center, packedNormal);
+ static TQuad fromUnaligned(ModelQuadFacing facing, float[] extents, float[] vertexPositions, Vector3fc center, int packedNormal) {
+ return new TQuad(facing, extents, vertexPositions, center, packedNormal);
}
public ModelQuadFacing getFacing() {
@@ -73,9 +78,9 @@ public ModelQuadFacing useQuantizedFacing() {
this.getQuantizedNormal();
this.facing = ModelQuadFacing.fromNormal(this.quantizedNormal.x(), this.quantizedNormal.y(), this.quantizedNormal.z());
if (this.facing.isAligned()) {
- this.dotProduct = getAlignedDotProduct(this.facing, this.extents);
+ this.quantizedDotProduct = getAlignedDotProduct(this.facing, this.extents);
} else {
- this.dotProduct = this.getCenter().dot(this.quantizedNormal);
+ this.quantizedDotProduct = this.getCenter().dot(this.quantizedNormal);
}
}
@@ -86,6 +91,31 @@ public float[] getExtents() {
return this.extents;
}
+ public float[] getVertexPositions() {
+ // calculate vertex positions from extents if there's no cached value
+ // (we don't want to be preemptively collecting vertex positions for all aligned quads)
+ if (this.vertexPositions == null) {
+ this.vertexPositions = new float[12];
+
+ var facingAxis = this.facing.getAxis();
+ var xRange = facingAxis == 0 ? 0 : 3;
+ var yRange = facingAxis == 1 ? 0 : 3;
+ var zRange = facingAxis == 2 ? 0 : 3;
+
+ var itemIndex = 0;
+ for (int x = 0; x <= xRange; x += 3) {
+ for (int y = 0; y <= yRange; y += 3) {
+ for (int z = 0; z <= zRange; z += 3) {
+ this.vertexPositions[itemIndex++] = this.extents[x];
+ this.vertexPositions[itemIndex++] = this.extents[y + 1];
+ this.vertexPositions[itemIndex++] = this.extents[z + 2];
+ }
+ }
+ }
+ }
+ return this.vertexPositions;
+ }
+
public Vector3fc getCenter() {
// calculate aligned quad center on demand
if (this.center == null) {
@@ -97,8 +127,12 @@ public Vector3fc getCenter() {
return this.center;
}
- public float getDotProduct() {
- return this.dotProduct;
+ public float getAccurateDotProduct() {
+ return this.accurateDotProduct;
+ }
+
+ public float getQuantizedDotProduct() {
+ return this.quantizedDotProduct;
}
public int getPackedNormal() {
@@ -116,6 +150,20 @@ public Vector3fc getQuantizedNormal() {
return this.quantizedNormal;
}
+ public Vector3fc getAccurateNormal() {
+ if (this.facing.isAligned()) {
+ return this.facing.getAlignedNormal();
+ } else {
+ if (this.accurateNormal == null) {
+ this.accurateNormal = new Vector3f(
+ NormI8.unpackX(this.packedNormal),
+ NormI8.unpackY(this.packedNormal),
+ NormI8.unpackZ(this.packedNormal));
+ }
+ return this.accurateNormal;
+ }
+ }
+
private void computeQuantizedNormal() {
float normX = NormI8.unpackX(this.packedNormal);
float normY = NormI8.unpackY(this.packedNormal);
@@ -150,7 +198,7 @@ int getQuadHash() {
} else {
result = 31 * result + this.packedNormal;
}
- result = 31 * result + Float.hashCode(this.dotProduct);
+ result = 31 * result + Float.hashCode(this.quantizedDotProduct);
return result;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java
index 6edd1314e7..8ba267a3e6 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java
@@ -116,7 +116,7 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M
float lastX = vertices[3].x;
float lastY = vertices[3].y;
float lastZ = vertices[3].z;
- int uniqueQuads = 0;
+ int uniqueVertexes = 0;
float posXExtent = Float.NEGATIVE_INFINITY;
float posYExtent = Float.NEGATIVE_INFINITY;
@@ -141,7 +141,7 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M
xSum += x;
ySum += y;
zSum += z;
- uniqueQuads++;
+ uniqueVertexes++;
}
if (i != 3) {
lastX = x;
@@ -185,13 +185,38 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M
}
Vector3fc center = null;
- if (!facing.isAligned() || uniqueQuads != 4) {
- var centerX = xSum / uniqueQuads;
- var centerY = ySum / uniqueQuads;
- var centerZ = zSum / uniqueQuads;
+ if (!facing.isAligned() || uniqueVertexes != 4) {
+ var centerX = xSum / uniqueVertexes;
+ var centerY = ySum / uniqueVertexes;
+ var centerZ = zSum / uniqueVertexes;
center = new Vector3f(centerX, centerY, centerZ);
}
+ // check if we need to store vertex positions for this quad, only necessary if it's unaligned or rotated (yet aligned)
+ var needsVertexPositions = uniqueVertexes != 4 || !facing.isAligned();
+ if (!needsVertexPositions) {
+ for (int i = 0; i < 4; i++) {
+ var vertex = vertices[i];
+ if (vertex.x != posYExtent && vertex.x != negYExtent ||
+ vertex.y != posZExtent && vertex.y != negZExtent ||
+ vertex.z != posXExtent && vertex.z != negXExtent) {
+ needsVertexPositions = true;
+ break;
+ }
+ }
+ }
+
+ float[] vertexPositions = null;
+ if (needsVertexPositions) {
+ vertexPositions = new float[12];
+ for (int i = 0, itemIndex = 0; i < 4; i++) {
+ var vertex = vertices[i];
+ vertexPositions[itemIndex++] = vertex.x;
+ vertexPositions[itemIndex++] = vertex.y;
+ vertexPositions[itemIndex++] = vertex.z;
+ }
+ }
+
if (facing.isAligned()) {
// only update global extents if there are no unaligned quads since this is only
// used for the convex box test which doesn't work with unaligned quads anyway
@@ -204,11 +229,11 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M
this.extents[5] = Math.min(this.extents[5], negZExtent);
}
- var quad = TQuad.fromAligned(facing, extents, center);
+ var quad = TQuad.fromAligned(facing, extents, vertexPositions, center);
quadList.add(quad);
var extreme = this.alignedExtremes[direction];
- var distance = quad.getDotProduct();
+ var distance = quad.getAccurateDotProduct();
// check if this is a new dot product for this distance
var existingExtreme = this.alignedExtremes[direction];
@@ -225,11 +250,11 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M
} else {
this.hasUnaligned = true;
- var quad = TQuad.fromUnaligned(facing, extents, center, packedNormal);
+ var quad = TQuad.fromUnaligned(facing, extents, vertexPositions, center, packedNormal);
quadList.add(quad);
// update the two unaligned normals that are tracked
- var distance = quad.getDotProduct();
+ var distance = quad.getAccurateDotProduct();
if (packedNormal == this.unalignedANormal) {
if (Float.isNaN(this.unalignedADistance1)) {
this.unalignedADistance1 = distance;
@@ -536,7 +561,7 @@ public TranslucentData getTranslucentData(
return NoData.forNoTranslucent(this.sectionPos);
}
- var vertexCounts = translucentMesh.getVertexCounts();
+ var vertexCounts = translucentMesh.computeVertexCounts();
// re-use the original translucent data if it's the same. This reduces the
// amount of generated and uploaded index data when sections are rebuilt without
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java
index 20a48780d1..6362cbc07f 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java
@@ -65,7 +65,7 @@ private static boolean doubleLeafPossible(TQuad quadA, TQuad quadB) {
// opposite normal (distance irrelevant)
if (NormI8.isOpposite(packedNormalA, packedNormalB)
// same normal and same distance
- || packedNormalA == packedNormalB && quadA.getDotProduct() == quadB.getDotProduct()) {
+ || packedNormalA == packedNormalB && quadA.getAccurateDotProduct() == quadB.getAccurateDotProduct()) {
return true;
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java
index f56c9a501d..3ee6572093 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java
@@ -540,7 +540,7 @@ static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArra
for (int i = 0; i < indexes.size(); i++) {
var quadIndex = indexes.getInt(i);
- keys[i] = MathUtil.floatToComparableInt(workspace.quads[quadIndex].getDotProduct());
+ keys[i] = MathUtil.floatToComparableInt(workspace.quads[quadIndex].getAccurateDotProduct());
}
quadIndexes = RadixSort.sort(keys);
@@ -553,7 +553,7 @@ static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArra
for (int i = 0; i < indexes.size(); i++) {
var quadIndex = indexes.getInt(i);
- int dotProductComponent = MathUtil.floatToComparableInt(workspace.quads[quadIndex].getDotProduct());
+ int dotProductComponent = MathUtil.floatToComparableInt(workspace.quads[quadIndex].getAccurateDotProduct());
sortData[i] = (long) dotProductComponent << 32 | quadIndex;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java
index 34f923ff02..a8fff490e0 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java
@@ -46,21 +46,7 @@ public Sorter getSorter() {
public static AnyOrderData fromMesh(int[] vertexCounts,
TQuad[] quads, SectionPos sectionPos) {
var anyOrderData = new AnyOrderData(sectionPos, vertexCounts, quads.length);
- var sorter = new StaticSorter(quads.length);
- anyOrderData.sorterOnce = sorter;
- var indexBuffer = sorter.getIntBuffer();
-
- for (var vertexCount : vertexCounts) {
- if (vertexCount <= 0) {
- continue;
- }
-
- int count = TranslucentData.vertexCountToQuadCount(vertexCount);
- for (int i = 0; i < count; i++) {
- TranslucentData.writeQuadVertexIndexes(indexBuffer, i);
- }
- }
-
+ anyOrderData.sorterOnce = new SharedIndexSorter(quads.length);
return anyOrderData;
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java
index 87539c346d..d184fb4ad3 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java
@@ -1,6 +1,6 @@
package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
-abstract class DynamicSorter extends Sorter {
+abstract class DynamicSorter extends PresentSorter {
private final int quadCount;
DynamicSorter(int quadCount) {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java
index 7b5fc1e491..200ec74a93 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java
@@ -1,7 +1,6 @@
package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
-import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.GeometryPlanes;
import net.caffeinemc.mods.sodium.client.util.sorting.RadixSort;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java
new file mode 100644
index 0000000000..81358a07a4
--- /dev/null
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java
@@ -0,0 +1,23 @@
+package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
+
+import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
+
+public abstract class PresentSorter implements Sorter {
+ private NativeBuffer indexBuffer;
+
+ @Override
+ public NativeBuffer getIndexBuffer() {
+ return this.indexBuffer;
+ }
+
+ void initBufferWithQuadLength(int quadCount) {
+ this.indexBuffer = new NativeBuffer(TranslucentData.quadCountToIndexBytes(quadCount));
+ }
+
+ @Override
+ public void destroy() {
+ if (this.indexBuffer != null) {
+ this.indexBuffer.free();
+ }
+ }
+}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java
new file mode 100644
index 0000000000..9dc709a761
--- /dev/null
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java
@@ -0,0 +1,27 @@
+package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
+
+import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
+
+import java.nio.IntBuffer;
+
+public record SharedIndexSorter(int quadCount) implements Sorter {
+ @Override
+ public NativeBuffer getIndexBuffer() {
+ return null;
+ }
+
+ @Override
+ public IntBuffer getIntBuffer() {
+ return null;
+ }
+
+ @Override
+ public void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial) {
+ // no-op
+ }
+
+ @Override
+ public void destroy() {
+ // no-op
+ }
+}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java
deleted file mode 100644
index c543a82335..0000000000
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
-
-public interface SortData extends PresentSortData {
- boolean isReusingUploadedIndexData();
-}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java
index 1103773793..545e58a2b9 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java
@@ -1,18 +1,7 @@
package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
-import net.caffeinemc.mods.sodium.client.util.NativeBuffer;
+public interface Sorter extends PresentSortData {
+ void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial);
-public abstract class Sorter implements PresentSortData {
- private NativeBuffer indexBuffer;
-
- public abstract void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial);
-
- @Override
- public NativeBuffer getIndexBuffer() {
- return this.indexBuffer;
- }
-
- void initBufferWithQuadLength(int quadCount) {
- this.indexBuffer = new NativeBuffer(TranslucentData.quadCountToIndexBytes(quadCount));
- }
+ void destroy();
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java
index e0f253697f..888ccd2034 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java
@@ -1,6 +1,5 @@
package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
-import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad;
import net.caffeinemc.mods.sodium.client.util.MathUtil;
@@ -50,7 +49,7 @@ private static StaticNormalRelativeData fromDoubleUnaligned(int[] vertexCounts,
final var keys = new int[quads.length];
for (int q = 0; q < quads.length; q++) {
- keys[q] = MathUtil.floatToComparableInt(quads[q].getDotProduct());
+ keys[q] = MathUtil.floatToComparableInt(quads[q].getAccurateDotProduct());
}
var indices = RadixSort.sort(keys);
@@ -62,7 +61,7 @@ private static StaticNormalRelativeData fromDoubleUnaligned(int[] vertexCounts,
final var sortData = new long[quads.length];
for (int q = 0; q < quads.length; q++) {
- int dotProductComponent = MathUtil.floatToComparableInt(quads[q].getDotProduct());
+ int dotProductComponent = MathUtil.floatToComparableInt(quads[q].getAccurateDotProduct());
sortData[q] = (long) dotProductComponent << 32 | q;
}
@@ -116,7 +115,7 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts,
final var keys = new int[count];
for (int q = 0; q < count; q++) {
- keys[q] = MathUtil.floatToComparableInt(quads[quadIndex++].getDotProduct());
+ keys[q] = MathUtil.floatToComparableInt(quads[quadIndex++].getAccurateDotProduct());
}
var indices = RadixSort.sort(keys);
@@ -127,7 +126,7 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts,
} else {
for (int i = 0; i < count; i++) {
var quad = quads[quadIndex++];
- int dotProductComponent = MathUtil.floatToComparableInt(quad.getDotProduct());
+ int dotProductComponent = MathUtil.floatToComparableInt(quad.getAccurateDotProduct());
sortData[i] = (long) dotProductComponent << 32 | i;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java
index fe6bc4d133..cbc09e19ed 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java
@@ -1,6 +1,6 @@
package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data;
-class StaticSorter extends Sorter {
+class StaticSorter extends PresentSorter {
StaticSorter(int quadCount) {
this.initBufferWithQuadLength(quadCount);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java
index d45af2c756..eb8e9b3dea 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java
@@ -19,6 +19,8 @@
* significantly more robust topo sorting.
*/
public class TopoGraphSorting {
+ private static final float HALF_SPACE_EPSILON = 0.001f;
+
private TopoGraphSorting() {
}
@@ -35,8 +37,36 @@ private static boolean pointOutsideHalfSpace(float planeDistance, Vector3fc plan
return planeNormal.dot(point) > planeDistance;
}
- private static boolean pointInsideHalfSpace(float planeDistance, Vector3fc planeNormal, Vector3fc point) {
- return planeNormal.dot(point) < planeDistance;
+ /**
+ * Test if the given point is within the half space defined by the plane anchor and the plane normal. The normal points away from the space considered to be inside.
+ *
+ * A small epsilon is added in the test to account for floating point errors, making it harder for a point on the edge to be considered inside.
+ *
+ * @param planeDistance dot product of the plane
+ * @param planeNormal the normal of the plane
+ * @param x x coordinate of the point
+ * @param y y coordinate of the point
+ * @param z z coordinate of the point
+ * @return true if the point is inside the half space
+ */
+ private static boolean pointInsideHalfSpaceEpsilon(float planeDistance, Vector3fc planeNormal, float x, float y, float z) {
+ return planeNormal.dot(x, y, z) + HALF_SPACE_EPSILON < planeDistance;
+ }
+
+ /**
+ * Test if the given point is outside the half space defined by the plane anchor and the plane normal. The normal points away from the space considered to be inside.
+ *
+ * A small epsilon is subtracted in the test to account for floating point errors, making it harder for a point on the edge to be considered outside.
+ *
+ * @param planeDistance dot product of the plane
+ * @param planeNormal the normal of the plane
+ * @param x x coordinate of the point
+ * @param y y coordinate of the point
+ * @param z z coordinate of the point
+ * @return true if the point is inside the half space
+ */
+ private static boolean pointOutsideHalfSpaceEpsilon(float planeDistance, Vector3fc planeNormal, float x, float y, float z) {
+ return planeNormal.dot(x, y, z) - HALF_SPACE_EPSILON > planeDistance;
}
public static boolean orthogonalQuadVisibleThrough(TQuad quadA, TQuad quadB) {
@@ -127,12 +157,9 @@ private static boolean visibilityWithSeparator(TQuad quadA, TQuad quadB,
* Checks if one quad is visible through the other quad. This accepts arbitrary
* quads, even unaligned ones.
*
- * @param quad the quad through which the other quad is being
- * tested
+ * @param quad the quad through which the other quad is being tested
* @param other the quad being tested
- * @param distancesByNormal a map of normals to sorted arrays of face plane
- * distances for disproving that the quads are visible
- * through each other, null to disable
+ * @param distancesByNormal a map of normals to sorted arrays of face plane distances for disproving that the quads are visible through each other, null to disable
* @return true if the other quad is visible through the first quad
*/
private static boolean quadVisibleThrough(TQuad quad, TQuad other,
@@ -141,11 +168,12 @@ private static boolean quadVisibleThrough(TQuad quad, TQuad other,
return false;
}
- // aligned quads
- var quadFacing = quad.useQuantizedFacing();
- var otherFacing = other.useQuantizedFacing();
- boolean result;
+ var quadFacing = quad.getFacing();
+ var otherFacing = other.getFacing();
+ boolean result = false;
if (quadFacing != ModelQuadFacing.UNASSIGNED && otherFacing != ModelQuadFacing.UNASSIGNED) {
+ // aligned quads
+
// opposites never see each other
if (quadFacing.getOpposite() == otherFacing) {
return false;
@@ -162,15 +190,45 @@ private static boolean quadVisibleThrough(TQuad quad, TQuad other,
}
} else {
// at least one unaligned quad
- // this is an approximation since our quads don't store all their vertices.
- // check that other center is within the half space of quad and that quad isn't
- // in the half space of other
- result = pointInsideHalfSpace(quad.getDotProduct(), quad.getQuantizedNormal(), other.getCenter())
- && !pointInsideHalfSpace(other.getDotProduct(), other.getQuantizedNormal(), quad.getCenter());
+
+ var quadDot = quad.getAccurateDotProduct();
+ var quadNormal = quad.getAccurateNormal();
+ var otherVertexPositions = other.getVertexPositions();
+
+ // at least one of the other quad's vertexes must be inside the half space of the first quad
+ var otherInsideQuad = false;
+ for (int i = 0, itemIndex = 0; i < 4; i++) {
+ if (pointInsideHalfSpaceEpsilon(quadDot, quadNormal,
+ otherVertexPositions[itemIndex++],
+ otherVertexPositions[itemIndex++],
+ otherVertexPositions[itemIndex++])) {
+ otherInsideQuad = true;
+ break;
+ }
+ }
+ if (otherInsideQuad) {
+ var otherDot = other.getAccurateDotProduct();
+ var otherNormal = other.getAccurateNormal();
+ var quadVertexPositions = quad.getVertexPositions();
+
+ // not all the quad's vertexes must be inside the half space of the other quad
+ // i.e. there must be at least one vertex outside the other quad
+ var quadNotFullyInsideOther = false;
+ for (int i = 0, itemIndex = 0; i < 4; i++) {
+ if (pointOutsideHalfSpaceEpsilon(otherDot, otherNormal,
+ quadVertexPositions[itemIndex++],
+ quadVertexPositions[itemIndex++],
+ quadVertexPositions[itemIndex++])) {
+ quadNotFullyInsideOther = true;
+ break;
+ }
+ }
+
+ result = quadNotFullyInsideOther;
+ }
}
- // if enabled and necessary, try to disprove this see-through relationship with
- // a separator plane
+ // if enabled and necessary, try to disprove this see-through relationship with a separator plane
if (result && distancesByNormal != null) {
return visibilityWithSeparator(quad, other, distancesByNormal, cameraPos);
}
@@ -209,10 +267,7 @@ public static boolean topoGraphSort(
for (int i = 0; i < allQuads.length; i++) {
TQuad quad = allQuads[i];
- // NOTE: This approximation may introduce wrong sorting if the real and the
- // quantized normal aren't the same. A quad may be ignored with the quantized
- // normal, but it's actually visible in camera.
- if (pointOutsideHalfSpace(quad.getDotProduct(), quad.getQuantizedNormal(), cameraPos)) {
+ if (pointOutsideHalfSpace(quad.getAccurateDotProduct(), quad.getAccurateNormal(), cameraPos)) {
activeToRealIndex[quadCount] = i;
quads[quadCount] = quad;
quadCount++;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java
index 8f75c2db7d..16acdf0230 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java
@@ -92,9 +92,9 @@ public void addUnalignedPlane(SectionPos sectionPos, Vector3fc normal, float dis
public void addQuadPlane(SectionPos sectionPos, TQuad quad) {
var facing = quad.useQuantizedFacing();
if (facing.isAligned()) {
- this.addAlignedPlane(sectionPos, facing.ordinal(), quad.getDotProduct());
+ this.addAlignedPlane(sectionPos, facing.ordinal(), quad.getQuantizedDotProduct());
} else {
- this.addUnalignedPlane(sectionPos, quad.getQuantizedNormal(), quad.getDotProduct());
+ this.addUnalignedPlane(sectionPos, quad.getQuantizedNormal(), quad.getQuantizedDotProduct());
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java
index 574701eda2..0b9771d600 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java
@@ -3,6 +3,7 @@
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
+import org.apache.commons.lang3.Validate;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
@@ -13,8 +14,9 @@ public class ChunkMeshBufferBuilder {
private final int initialCapacity;
private ByteBuffer buffer;
- private int count;
- private int capacity;
+ private int vertexCount;
+ private int vertexCapacity;
+
private int sectionIndex;
public ChunkMeshBufferBuilder(ChunkVertexType vertexType, int initialCapacity) {
@@ -23,7 +25,7 @@ public ChunkMeshBufferBuilder(ChunkVertexType vertexType, int initialCapacity) {
this.buffer = null;
- this.capacity = initialCapacity;
+ this.vertexCapacity = initialCapacity;
this.initialCapacity = initialCapacity;
}
@@ -32,36 +34,40 @@ public void push(ChunkVertexEncoder.Vertex[] vertices, Material material) {
}
public void push(ChunkVertexEncoder.Vertex[] vertices, int materialBits) {
- var vertexCount = vertices.length;
-
- if (this.count + vertexCount >= this.capacity) {
- this.grow(this.stride * vertexCount);
+ if (vertices.length != 4) {
+ throw new IllegalArgumentException("Only quad primitives (with 4 vertices) can be pushed");
}
- this.encoder.write(MemoryUtil.memAddress(this.buffer, this.count * this.stride),
- materialBits, vertices, this.sectionIndex);
+ this.ensureCapacity(4);
- this.count += vertexCount;
+ this.encoder.write(MemoryUtil.memAddress(this.buffer, this.vertexCount * this.stride),
+ materialBits, vertices, this.sectionIndex);
+ this.vertexCount += 4;
}
- private void grow(int len) {
- // The new capacity will at least as large as the write it needs to service
- int cap = Math.max(this.capacity * 2, this.capacity + len);
+ private void ensureCapacity(int vertexCount) {
+ if (this.vertexCount + vertexCount >= this.vertexCapacity) {
+ this.grow(vertexCount);
+ }
+ }
- // Update the buffer and capacity now
- this.setBufferSize(cap * this.stride);
+ private void grow(int vertexCount) {
+ this.reallocate(
+ // The new capacity will at least twice as large
+ Math.max(this.vertexCapacity * 2, this.vertexCapacity + vertexCount)
+ );
}
- private void setBufferSize(int capacity) {
- this.buffer = MemoryUtil.memRealloc(this.buffer, capacity * this.stride);
- this.capacity = capacity;
+ private void reallocate(int vertexCount) {
+ this.buffer = MemoryUtil.memRealloc(this.buffer, vertexCount * this.stride);
+ this.vertexCapacity = vertexCount;
}
public void start(int sectionIndex) {
- this.count = 0;
+ this.vertexCount = 0;
this.sectionIndex = sectionIndex;
- this.setBufferSize(this.initialCapacity);
+ this.reallocate(this.initialCapacity);
}
public void destroy() {
@@ -73,7 +79,7 @@ public void destroy() {
}
public boolean isEmpty() {
- return this.count == 0;
+ return this.vertexCount == 0;
}
public ByteBuffer slice() {
@@ -81,10 +87,10 @@ public ByteBuffer slice() {
throw new IllegalStateException("No vertex data in buffer");
}
- return MemoryUtil.memSlice(this.buffer, 0, this.stride * this.count);
+ return MemoryUtil.memSlice(this.buffer, 0, this.stride * this.vertexCount);
}
public int count() {
- return this.count;
+ return this.vertexCount;
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java
index 627391c3e1..44c73a8719 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java
@@ -1,10 +1,10 @@
package net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.impl;
+import net.caffeinemc.mods.sodium.api.util.ColorARGB;
import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexFormat;
import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderBindingPoints;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder;
import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType;
-import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper;
import net.minecraft.util.Mth;
import org.lwjgl.system.MemoryUtil;
@@ -12,8 +12,7 @@ public class CompactChunkVertex implements ChunkVertexType {
public static final int STRIDE = 20;
public static final GlVertexFormat VERTEX_FORMAT = GlVertexFormat.builder(STRIDE)
- .addElement(DefaultChunkMeshAttributes.POSITION_HI, ChunkShaderBindingPoints.ATTRIBUTE_POSITION_HI, 0)
- .addElement(DefaultChunkMeshAttributes.POSITION_LO, ChunkShaderBindingPoints.ATTRIBUTE_POSITION_LO, 4)
+ .addElement(DefaultChunkMeshAttributes.POSITION, ChunkShaderBindingPoints.ATTRIBUTE_POSITION, 0)
.addElement(DefaultChunkMeshAttributes.COLOR, ChunkShaderBindingPoints.ATTRIBUTE_COLOR, 8)
.addElement(DefaultChunkMeshAttributes.TEXTURE, ChunkShaderBindingPoints.ATTRIBUTE_TEXTURE, 12)
.addElement(DefaultChunkMeshAttributes.LIGHT_MATERIAL_INDEX, ChunkShaderBindingPoints.ATTRIBUTE_LIGHT_MATERIAL_INDEX, 16)
@@ -59,7 +58,7 @@ public ChunkVertexEncoder getEncoder() {
MemoryUtil.memPutInt(ptr + 0L, packPositionHi(x, y, z));
MemoryUtil.memPutInt(ptr + 4L, packPositionLo(x, y, z));
- MemoryUtil.memPutInt(ptr + 8L, ColorHelper.multiplyRGB(vertex.color, vertex.ao));
+ MemoryUtil.memPutInt(ptr + 8L, ColorARGB.mulRGB(vertex.color, vertex.ao));
MemoryUtil.memPutInt(ptr + 12L, packTexture(u, v));
MemoryUtil.memPutInt(ptr + 16L, packLightAndData(light, materialBits, section));
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/DefaultChunkMeshAttributes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/DefaultChunkMeshAttributes.java
index 5da6117115..04170bc924 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/DefaultChunkMeshAttributes.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/DefaultChunkMeshAttributes.java
@@ -4,8 +4,7 @@
import net.caffeinemc.mods.sodium.client.render.vertex.VertexFormatAttribute;
public class DefaultChunkMeshAttributes {
- public static final VertexFormatAttribute POSITION_HI = new VertexFormatAttribute("POSITION_HI", GlVertexAttributeFormat.UNSIGNED_INT, 1, false, true);
- public static final VertexFormatAttribute POSITION_LO = new VertexFormatAttribute("POSITION_LO", GlVertexAttributeFormat.UNSIGNED_INT, 1, false, true);
+ public static final VertexFormatAttribute POSITION = new VertexFormatAttribute("POSITION", GlVertexAttributeFormat.UNSIGNED_INT, 2, false, true);
public static final VertexFormatAttribute COLOR = new VertexFormatAttribute("COLOR", GlVertexAttributeFormat.UNSIGNED_BYTE, 4, true, false);
public static final VertexFormatAttribute TEXTURE = new VertexFormatAttribute("TEXTURE", GlVertexAttributeFormat.UNSIGNED_SHORT, 2, false, true);
public static final VertexFormatAttribute LIGHT_MATERIAL_INDEX = new VertexFormatAttribute("LIGHT_MATERIAL_INDEX", GlVertexAttributeFormat.UNSIGNED_BYTE, 4, false, true);
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java
index 7b39a52063..1c6097574b 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java
@@ -18,11 +18,11 @@
import net.caffeinemc.mods.sodium.client.render.frapi.material.MaterialFinderImpl;
import net.caffeinemc.mods.sodium.client.render.frapi.material.RenderMaterialImpl;
-import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MeshBuilderImpl;
+import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableMeshImpl;
import net.fabricmc.fabric.api.renderer.v1.Renderer;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
-import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
+import net.fabricmc.fabric.api.renderer.v1.mesh.MutableMesh;
import net.minecraft.resources.ResourceLocation;
import java.util.HashMap;
@@ -36,7 +36,7 @@ public class SodiumRenderer implements Renderer {
public static final RenderMaterial STANDARD_MATERIAL = INSTANCE.materialFinder().find();
static {
- INSTANCE.registerMaterial(RenderMaterial.MATERIAL_STANDARD, STANDARD_MATERIAL);
+ INSTANCE.registerMaterial(RenderMaterial.STANDARD_ID, STANDARD_MATERIAL);
}
private final HashMap materialMap = new HashMap<>();
@@ -44,8 +44,8 @@ public class SodiumRenderer implements Renderer {
private SodiumRenderer() { }
@Override
- public MeshBuilder meshBuilder() {
- return new MeshBuilderImpl();
+ public MutableMesh mutableMesh() {
+ return new MutableMeshImpl();
}
@Override
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java
index 9961263598..2acf626078 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java
@@ -16,7 +16,8 @@
package net.caffeinemc.mods.sodium.client.render.frapi.helper;
-import java.nio.ByteOrder;
+import net.caffeinemc.mods.sodium.api.util.ColorABGR;
+import net.caffeinemc.mods.sodium.api.util.ColorARGB;
/**
* Static routines of general utility for renderer implementations.
@@ -24,44 +25,9 @@
* designed to be usable without the default renderer.
*/
public abstract class ColorHelper {
- private ColorHelper() { }
-
- private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
-
- /** Component-wise multiply. Components need to be in same order in both inputs! */
- public static int multiplyColor(int color1, int color2) {
- if (color1 == -1) {
- return color2;
- } else if (color2 == -1) {
- return color1;
- }
-
- final int alpha = ((color1 >>> 24) & 0xFF) * ((color2 >>> 24) & 0xFF) / 0xFF;
- final int red = ((color1 >>> 16) & 0xFF) * ((color2 >>> 16) & 0xFF) / 0xFF;
- final int green = ((color1 >>> 8) & 0xFF) * ((color2 >>> 8) & 0xFF) / 0xFF;
- final int blue = (color1 & 0xFF) * (color2 & 0xFF) / 0xFF;
-
- return (alpha << 24) | (red << 16) | (green << 8) | blue;
- }
-
- /** Multiplies three lowest components by shade. High byte (usually alpha) unchanged. */
- public static int multiplyRGB(int color, float shade) {
- final int alpha = ((color >>> 24) & 0xFF);
- final int red = (int) (((color >>> 16) & 0xFF) * shade);
- final int green = (int) (((color >>> 8) & 0xFF) * shade);
- final int blue = (int) ((color & 0xFF) * shade);
-
- return (alpha << 24) | (red << 16) | (green << 8) | blue;
- }
-
- /**
- * Component-wise max.
- */
public static int maxBrightness(int b0, int b1) {
- if (b0 == 0) return b1;
- if (b1 == 0) return b0;
-
- return Math.max(b0 & 0xFFFF, b1 & 0xFFFF) | Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000);
+ return Math.max(b0 & 0x0000FFFF, b1 & 0x0000FFFF) |
+ Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000);
}
/*
@@ -81,36 +47,16 @@ Vanilla color format (big endian): RGBA (0xRRGGBBAA)
*/
/**
- * Converts from ARGB color to ABGR color if little endian or RGBA color if big endian.
+ * Converts from ARGB color to ABGR color. The result will be in the platform's native byte order.
*/
public static int toVanillaColor(int color) {
- if (color == -1) {
- return -1;
- }
-
- if (BIG_ENDIAN) {
- // ARGB to RGBA
- return ((color & 0x00FFFFFF) << 8) | ((color & 0xFF000000) >>> 24);
- } else {
- // ARGB to ABGR
- return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16);
- }
+ return ColorABGR.toNativeByteOrder(ColorARGB.toABGR(color));
}
/**
- * Converts to ARGB color from ABGR color if little endian or RGBA color if big endian.
+ * Converts from ABGR color to ARGB color. The input should be in the platform's native byte order.
*/
public static int fromVanillaColor(int color) {
- if (color == -1) {
- return -1;
- }
-
- if (BIG_ENDIAN) {
- // RGBA to ARGB
- return ((color & 0xFFFFFF00) >>> 8) | ((color & 0x000000FF) << 24);
- } else {
- // ABGR to ARGB
- return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16);
- }
+ return ColorARGB.fromABGR(ColorABGR.fromNativeByteOrder(color));
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/GeometryHelper.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/GeometryHelper.java
index da25fe4eda..e1e0ef1f97 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/GeometryHelper.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/GeometryHelper.java
@@ -21,6 +21,7 @@
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import org.joml.Vector3f;
+import org.joml.Vector3fc;
/**
* Static routines of general utility for renderer implementations.
@@ -49,7 +50,7 @@ public static boolean isQuadParallelToFace(Direction face, QuadView quad) {
*
Derived from the quad face normal and expects convex quads with all points co-planar.
*/
public static Direction lightFace(QuadView quad) {
- final Vector3f normal = quad.faceNormal();
+ final Vector3fc normal = quad.faceNormal();
return switch (GeometryHelper.longestAxis(normal)) {
case X -> normal.x() > 0 ? Direction.EAST : Direction.WEST;
case Y -> normal.y() > 0 ? Direction.UP : Direction.DOWN;
@@ -63,7 +64,7 @@ public static Direction lightFace(QuadView quad) {
/**
* @see #longestAxis(float, float, float)
*/
- public static Direction.Axis longestAxis(Vector3f vec) {
+ public static Direction.Axis longestAxis(Vector3fc vec) {
return longestAxis(vec.x(), vec.y(), vec.z());
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/NormalHelper.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/NormalHelper.java
index f2fd5a5486..cd287099f1 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/NormalHelper.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/NormalHelper.java
@@ -42,7 +42,7 @@ public static void computeFaceNormal(@NotNull Vector3f saveTo, QuadView q) {
final Direction nominalFace = q.nominalFace();
if (nominalFace != null && GeometryHelper.isQuadParallelToFace(nominalFace, q)) {
- Vec3i vec = nominalFace.getNormal();
+ Vec3i vec = nominalFace.getUnitVec3i();
saveTo.set(vec.getX(), vec.getY(), vec.getZ());
return;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java
index 44a5b97f83..105cd02a1c 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java
@@ -27,7 +27,6 @@ public class MaterialFinderImpl extends MaterialViewImpl implements MaterialFind
static {
MaterialFinderImpl finder = new MaterialFinderImpl();
finder.ambientOcclusion(TriState.DEFAULT);
- finder.glint(TriState.DEFAULT);
finder.shadeMode(ShadeMode.ENHANCED);
defaultBits = finder.bits;
@@ -48,12 +47,6 @@ public MaterialFinder blendMode(BlendMode blendMode) {
return this;
}
- @Override
- public MaterialFinder disableColorIndex(boolean disable) {
- bits = disable ? (bits | COLOR_DISABLE_FLAG) : (bits & ~COLOR_DISABLE_FLAG);
- return this;
- }
-
@Override
public MaterialFinder emissive(boolean isEmissive) {
bits = isEmissive ? (bits | EMISSIVE_FLAG) : (bits & ~EMISSIVE_FLAG);
@@ -75,10 +68,10 @@ public MaterialFinder ambientOcclusion(TriState mode) {
}
@Override
- public MaterialFinder glint(TriState mode) {
- Objects.requireNonNull(mode, "glint TriState may not be null");
+ public MaterialFinder glintMode(GlintMode mode) {
+ Objects.requireNonNull(mode, "GlintMode may not be null");
- bits = (bits & ~GLINT_MASK) | (mode.ordinal() << GLINT_BIT_OFFSET);
+ bits = (bits & ~GLINT_MODE_MASK) | (mode.ordinal() << GLINT_MODE_BIT_OFFSET);
return this;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java
index e6a4ea1e2e..9bd55d47e2 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java
@@ -17,6 +17,7 @@
package net.caffeinemc.mods.sodium.client.render.frapi.material;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
+import net.fabricmc.fabric.api.renderer.v1.material.GlintMode;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialView;
import net.fabricmc.fabric.api.renderer.v1.material.ShadeMode;
import net.fabricmc.fabric.api.util.TriState;
@@ -33,32 +34,32 @@ public class MaterialViewImpl implements MaterialView {
private static final int BLEND_MODE_COUNT = BLEND_MODES.length;
private static final TriState[] TRI_STATES = TriState.values();
private static final int TRI_STATE_COUNT = TRI_STATES.length;
+ private static final GlintMode[] GLINT_MODES = GlintMode.values();
+ private static final int GLINT_MODE_COUNT = GLINT_MODES.length;
private static final ShadeMode[] SHADE_MODES = ShadeMode.values();
private static final int SHADE_MODE_COUNT = SHADE_MODES.length;
protected static final int BLEND_MODE_BIT_LENGTH = Mth.ceillog2(BLEND_MODE_COUNT);
- protected static final int COLOR_DISABLE_BIT_LENGTH = 1;
protected static final int EMISSIVE_BIT_LENGTH = 1;
protected static final int DIFFUSE_BIT_LENGTH = 1;
protected static final int AO_BIT_LENGTH = Mth.ceillog2(TRI_STATE_COUNT);
- protected static final int GLINT_BIT_LENGTH = Mth.ceillog2(TRI_STATE_COUNT);
+ protected static final int GLINT_MODE_BIT_LENGTH = Mth.ceillog2(GLINT_MODE_COUNT);
protected static final int SHADE_MODE_BIT_LENGTH = Mth.ceillog2(SHADE_MODE_COUNT);
protected static final int BLEND_MODE_BIT_OFFSET = 0;
- protected static final int COLOR_DISABLE_BIT_OFFSET = BLEND_MODE_BIT_OFFSET + BLEND_MODE_BIT_LENGTH;
- protected static final int EMISSIVE_BIT_OFFSET = COLOR_DISABLE_BIT_OFFSET + COLOR_DISABLE_BIT_LENGTH;
+ protected static final int EMISSIVE_BIT_OFFSET = BLEND_MODE_BIT_OFFSET + BLEND_MODE_BIT_LENGTH;
protected static final int DIFFUSE_BIT_OFFSET = EMISSIVE_BIT_OFFSET + EMISSIVE_BIT_LENGTH;
protected static final int AO_BIT_OFFSET = DIFFUSE_BIT_OFFSET + DIFFUSE_BIT_LENGTH;
protected static final int GLINT_BIT_OFFSET = AO_BIT_OFFSET + AO_BIT_LENGTH;
- protected static final int SHADE_MODE_BIT_OFFSET = GLINT_BIT_OFFSET + GLINT_BIT_LENGTH;
+ protected static final int GLINT_MODE_BIT_OFFSET = AO_BIT_OFFSET + AO_BIT_LENGTH;
+ protected static final int SHADE_MODE_BIT_OFFSET = GLINT_MODE_BIT_OFFSET + GLINT_MODE_BIT_LENGTH;
protected static final int TOTAL_BIT_LENGTH = SHADE_MODE_BIT_OFFSET + SHADE_MODE_BIT_LENGTH;
protected static final int BLEND_MODE_MASK = bitMask(BLEND_MODE_BIT_LENGTH, BLEND_MODE_BIT_OFFSET);
- protected static final int COLOR_DISABLE_FLAG = bitMask(COLOR_DISABLE_BIT_LENGTH, COLOR_DISABLE_BIT_OFFSET);
protected static final int EMISSIVE_FLAG = bitMask(EMISSIVE_BIT_LENGTH, EMISSIVE_BIT_OFFSET);
protected static final int DIFFUSE_FLAG = bitMask(DIFFUSE_BIT_LENGTH, DIFFUSE_BIT_OFFSET);
protected static final int AO_MASK = bitMask(AO_BIT_LENGTH, AO_BIT_OFFSET);
- protected static final int GLINT_MASK = bitMask(GLINT_BIT_LENGTH, GLINT_BIT_OFFSET);
+ protected static final int GLINT_MODE_MASK = bitMask(GLINT_MODE_BIT_LENGTH, GLINT_MODE_BIT_OFFSET);
protected static final int SHADE_MODE_MASK = bitMask(SHADE_MODE_BIT_LENGTH, SHADE_MODE_BIT_OFFSET);
protected static int bitMask(int bitLength, int bitOffset) {
@@ -68,12 +69,12 @@ protected static int bitMask(int bitLength, int bitOffset) {
protected static boolean areBitsValid(int bits) {
int blendMode = (bits & BLEND_MODE_MASK) >>> BLEND_MODE_BIT_OFFSET;
int ao = (bits & AO_MASK) >>> AO_BIT_OFFSET;
- int glint = (bits & GLINT_MASK) >>> GLINT_BIT_OFFSET;
+ int glintMode = (bits & GLINT_MODE_MASK) >>> GLINT_MODE_BIT_OFFSET;
int shadeMode = (bits & SHADE_MODE_MASK) >>> SHADE_MODE_BIT_OFFSET;
return blendMode < BLEND_MODE_COUNT
&& ao < TRI_STATE_COUNT
- && glint < TRI_STATE_COUNT
+ && glintMode < GLINT_MODE_COUNT
&& shadeMode < SHADE_MODE_COUNT;
}
@@ -88,11 +89,6 @@ public BlendMode blendMode() {
return BLEND_MODES[(bits & BLEND_MODE_MASK) >>> BLEND_MODE_BIT_OFFSET];
}
- @Override
- public boolean disableColorIndex() {
- return (bits & COLOR_DISABLE_FLAG) != 0;
- }
-
@Override
public boolean emissive() {
return (bits & EMISSIVE_FLAG) != 0;
@@ -109,8 +105,8 @@ public TriState ambientOcclusion() {
}
@Override
- public TriState glint() {
- return TRI_STATES[(bits & GLINT_MASK) >>> GLINT_BIT_OFFSET];
+ public GlintMode glintMode() {
+ return GLINT_MODES[(bits & GLINT_MODE_MASK) >>> GLINT_MODE_BIT_OFFSET];
}
@Override
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java
index 760f8b4dac..91a202828a 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java
@@ -17,6 +17,7 @@
package net.caffeinemc.mods.sodium.client.render.frapi.material;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
+import net.fabricmc.fabric.api.util.TriState;
public class RenderMaterialImpl extends MaterialViewImpl implements RenderMaterial {
public static final int VALUE_COUNT = 1 << TOTAL_BIT_LENGTH;
@@ -49,4 +50,12 @@ public static RenderMaterialImpl setDisableDiffuse(RenderMaterialImpl material,
return material;
}
+
+ public static RenderMaterialImpl setAmbientOcclusion(RenderMaterialImpl material, TriState mode) {
+ if (material.ambientOcclusion() != mode) {
+ return byIndex((material.bits & ~AO_MASK) | (mode.ordinal() << AO_BIT_OFFSET));
+ }
+
+ return material;
+ }
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/EncodingFormat.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/EncodingFormat.java
index 583867ec87..3f40457ffe 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/EncodingFormat.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/EncodingFormat.java
@@ -21,6 +21,7 @@
import com.mojang.blaze3d.vertex.VertexFormat;
import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing;
import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFlags;
+import net.caffeinemc.mods.sodium.client.render.frapi.SodiumRenderer;
import net.caffeinemc.mods.sodium.client.render.frapi.material.RenderMaterialImpl;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
@@ -37,7 +38,7 @@ private EncodingFormat() { }
static final int HEADER_BITS = 0;
static final int HEADER_FACE_NORMAL = 1;
- static final int HEADER_COLOR_INDEX = 2;
+ static final int HEADER_TINT_INDEX = 2;
static final int HEADER_TAG = 3;
public static final int HEADER_STRIDE = 4;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java
deleted file mode 100644
index 34e69c8b1f..0000000000
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.caffeinemc.mods.sodium.client.render.frapi.mesh;
-
-import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
-import net.fabricmc.fabric.api.renderer.v1.mesh.MeshBuilder;
-import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
-
-/**
- * Our implementation of {@link MeshBuilder}, used for static mesh creation and baking.
- * Not much to it - mainly it just needs to grow the int[] array as quads are appended
- * and maintain/provide a properly-configured {@link net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView} instance.
- * All the encoding and other work is handled in the quad base classes.
- * The one interesting bit is in {@link Maker#emitDirectly()}.
- */
-public class MeshBuilderImpl implements MeshBuilder {
- private int[] data = new int[256];
- private int index = 0;
- private int limit = data.length;
- private final Maker maker = new Maker();
-
- public MeshBuilderImpl() {
- ensureCapacity(EncodingFormat.TOTAL_STRIDE);
- maker.data = data;
- maker.baseIndex = index;
- maker.clear();
- }
-
- protected void ensureCapacity(int stride) {
- if (stride > limit - index) {
- limit *= 2;
- final int[] bigger = new int[limit];
- System.arraycopy(data, 0, bigger, 0, index);
- data = bigger;
- maker.data = data;
- }
- }
-
- @Override
- public QuadEmitter getEmitter() {
- maker.clear();
- return maker;
- }
-
- @Override
- public Mesh build() {
- final int[] packed = new int[index];
- System.arraycopy(data, 0, packed, 0, index);
- index = 0;
- maker.baseIndex = index;
- maker.clear();
- return new MeshImpl(packed);
- }
-
- /**
- * Our base classes are used differently so we define final
- * encoding steps in subtypes. This will be a static mesh used
- * at render time so we want to capture all geometry now and
- * apply non-location-dependent lighting.
- */
- private class Maker extends MutableQuadViewImpl {
- @Override
- public void emitDirectly() {
- computeGeometry();
- index += EncodingFormat.TOTAL_STRIDE;
- ensureCapacity(EncodingFormat.TOTAL_STRIDE);
- baseIndex = index;
- }
- }
-}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java
index 60fa624c90..051d27a709 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java
@@ -16,9 +16,11 @@
package net.caffeinemc.mods.sodium.client.render.frapi.mesh;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import org.jetbrains.annotations.Range;
import java.util.function.Consumer;
@@ -28,17 +30,38 @@
*/
public class MeshImpl implements Mesh {
/** Used to satisfy external calls to {@link #forEach(Consumer)}. */
- private final ThreadLocal cursorPool = ThreadLocal.withInitial(QuadViewImpl::new);
+ private static final ThreadLocal> CURSOR_POOLS = ThreadLocal.withInitial(ObjectArrayList::new);
- final int[] data;
+ int[] data;
+ int limit;
MeshImpl(int[] data) {
this.data = data;
+ limit = data.length;
}
+ MeshImpl() {}
+
@Override
- public void forEach(Consumer consumer) {
- forEach(consumer, cursorPool.get());
+ @Range(from = 0, to = Integer.MAX_VALUE)
+ public int size() {
+ return limit / EncodingFormat.TOTAL_STRIDE;
+ }
+
+ @Override
+ public void forEach(Consumer super QuadView> action) {
+ ObjectArrayList pool = CURSOR_POOLS.get();
+ QuadViewImpl cursor;
+
+ if (pool.isEmpty()) {
+ cursor = new QuadViewImpl();
+ } else {
+ cursor = pool.pop();
+ }
+
+ forEach(action, cursor);
+
+ pool.push(cursor);
}
/**
@@ -46,30 +69,34 @@ public void forEach(Consumer consumer) {
* to avoid the performance hit of a thread-local lookup.
* Also means renderer can hold final references to quad buffers.
*/
- void forEach(Consumer consumer, QuadViewImpl cursor) {
- final int limit = data.length;
+ void forEach(Consumer super C> action, C cursor) {
+ final int limit = this.limit;
int index = 0;
cursor.data = this.data;
while (index < limit) {
cursor.baseIndex = index;
cursor.load();
- consumer.accept(cursor);
+ action.accept(cursor);
index += EncodingFormat.TOTAL_STRIDE;
}
+
+ cursor.data = null;
}
+ // TODO: This could be optimized by checking if the emitter is that of a MutableMeshImpl and if
+ // it has no transforms, in which case the entire data array can be copied in bulk.
@Override
public void outputTo(QuadEmitter emitter) {
MutableQuadViewImpl e = (MutableQuadViewImpl) emitter;
final int[] data = this.data;
- final int limit = data.length;
+ final int limit = this.limit;
int index = 0;
while (index < limit) {
System.arraycopy(data, index, e.data, e.baseIndex, EncodingFormat.TOTAL_STRIDE);
e.load();
- e.emitDirectly();
+ e.transformAndEmit();
index += EncodingFormat.TOTAL_STRIDE;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableMeshImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableMeshImpl.java
new file mode 100644
index 0000000000..1f58afcb7d
--- /dev/null
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableMeshImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.caffeinemc.mods.sodium.client.render.frapi.mesh;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
+import net.fabricmc.fabric.api.renderer.v1.mesh.MutableMesh;
+import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+
+import java.util.function.Consumer;
+
+/**
+ * Our implementation of {@link MutableMesh}, used for static mesh creation and baking.
+ * Not much to it - mainly it just needs to grow the int[] array as quads are appended
+ * and maintain/provide a properly-configured {@link net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView} instance.
+ * All the encoding and other work is handled in the quad base classes.
+ * The one interesting bit is in {@link MutableQuadViewImpl#emitDirectly()}.
+ */
+public class MutableMeshImpl extends MeshImpl implements MutableMesh {
+ private final MutableQuadViewImpl emitter = new MutableQuadViewImpl() {
+ @Override
+ protected void emitDirectly() {
+ // Necessary because the validity of geometry is not encoded; reading mesh data always
+ // uses QuadViewImpl#load(), which assumes valid geometry. Built immutable meshes
+ // should also have valid geometry for better performance.
+ computeGeometry();
+ limit += EncodingFormat.TOTAL_STRIDE;
+ ensureCapacity(EncodingFormat.TOTAL_STRIDE);
+ baseIndex = limit;
+ }
+};
+
+ public MutableMeshImpl() {
+ data = new int[8 * EncodingFormat.TOTAL_STRIDE];
+ limit = 0;
+
+ ensureCapacity(EncodingFormat.TOTAL_STRIDE);
+ emitter.data = data;
+ emitter.baseIndex = limit;
+ emitter.clear();
+ }
+
+ private void ensureCapacity(int stride) {
+ if (stride > data.length - limit) {
+ final int[] bigger = new int[data.length * 2];
+ System.arraycopy(data, 0, bigger, 0, limit);
+ data = bigger;
+ emitter.data = data;
+ }
+ }
+
+ @Override
+ public QuadEmitter emitter() {
+ emitter.clear();
+ return emitter;
+ }
+
+ @Override
+ public void forEachMutable(Consumer super MutableQuadView> action) {
+ // emitDirectly will not be called by forEach, so just reuse the main emitter.
+ forEach(action, emitter);
+ emitter.data = data;
+ emitter.baseIndex = limit;
+ }
+
+ @Override
+ public Mesh immutableCopy() {
+ final int[] packed = new int[limit];
+ System.arraycopy(data, 0, packed, 0, limit);
+ return new MeshImpl(packed);
+ }
+
+ @Override
+ public void clear() {
+ limit = 0;
+ emitter.baseIndex = limit;
+ emitter.clear();
+ }
+}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java
index 3ab4bc208f..fcde4314fa 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java
@@ -16,6 +16,7 @@
package net.caffeinemc.mods.sodium.client.render.frapi.mesh;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.caffeinemc.mods.sodium.api.util.NormI8;
import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView;
import net.caffeinemc.mods.sodium.client.render.frapi.SodiumRenderer;
@@ -24,8 +25,11 @@
import net.caffeinemc.mods.sodium.client.render.frapi.material.RenderMaterialImpl;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadTransform;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.fabricmc.fabric.api.renderer.v1.model.SpriteFinder;
+import net.minecraft.client.renderer.LightTexture;
+import net.fabricmc.fabric.api.util.TriState;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.Direction;
@@ -47,6 +51,42 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm
@Nullable
private TextureAtlasSprite cachedSprite;
+ protected static final QuadTransform NO_TRANSFORM = q -> true;
+
+ protected QuadTransform activeTransform = NO_TRANSFORM;
+ private final ObjectArrayList transformStack = new ObjectArrayList<>();
+ private final QuadTransform stackTransform = q -> {
+ int i = transformStack.size() - 1;
+
+ while (i >= 0) {
+ if (!transformStack.get(i--).transform(q)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ /** Used for quick clearing of quad buffers. Implicitly has invalid geometry. */
+ static final int[] DEFAULT = EMPTY.clone();
+
+ static {
+ MutableQuadViewImpl quad = new MutableQuadViewImpl() {
+ @Override
+ protected void emitDirectly() {
+ // This quad won't be emitted. It's only used to configure the default quad data.
+ }
+ };
+
+ // Start with all zeroes
+ quad.data = DEFAULT;
+ // Apply non-zero defaults
+ quad.color(-1, -1, -1, -1);
+ quad.cullFace(null);
+ quad.material(SodiumRenderer.STANDARD_MATERIAL);
+ quad.tintIndex(-1);
+ }
+
@Nullable
public TextureAtlasSprite cachedSprite() {
return cachedSprite;
@@ -67,14 +107,9 @@ public TextureAtlasSprite sprite(SpriteFinder finder) {
}
public void clear() {
- System.arraycopy(EMPTY, 0, data, baseIndex, EncodingFormat.TOTAL_STRIDE);
+ System.arraycopy(DEFAULT, 0, data, baseIndex, EncodingFormat.TOTAL_STRIDE);
isGeometryInvalid = true;
nominalFace = null;
- normalFlags(0);
- tag(0);
- colorIndex(-1);
- cullFace(null);
- material(SodiumRenderer.STANDARD_MATERIAL);
cachedSprite(null);
}
@@ -133,6 +168,32 @@ public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) {
return this;
}
+ @Override
+ public void pushTransform(QuadTransform transform) {
+ if (transform == null) {
+ throw new NullPointerException("QuadTransform cannot be null!");
+ }
+
+ transformStack.push(transform);
+
+ if (transformStack.size() == 1) {
+ activeTransform = transform;
+ } else if (transformStack.size() == 2) {
+ activeTransform = stackTransform;
+ }
+ }
+
+ @Override
+ public void popTransform() {
+ transformStack.pop();
+
+ if (transformStack.isEmpty()) {
+ activeTransform = NO_TRANSFORM;
+ } else if (transformStack.size() == 1) {
+ activeTransform = transformStack.getFirst();
+ }
+ }
+
/**
* Internal helper method. Copies face normals to vertex normals lacking one.
*/
@@ -167,17 +228,13 @@ public final MutableQuadViewImpl nominalFace(@Nullable Direction face) {
@Override
public final MutableQuadViewImpl material(RenderMaterial material) {
- if (material == null) {
- material = SodiumRenderer.STANDARD_MATERIAL;
- }
-
data[baseIndex + HEADER_BITS] = EncodingFormat.material(data[baseIndex + HEADER_BITS], (RenderMaterialImpl) material);
return this;
}
@Override
- public final MutableQuadViewImpl colorIndex(int colorIndex) {
- data[baseIndex + HEADER_COLOR_INDEX] = colorIndex;
+ public final MutableQuadViewImpl tintIndex(int tintIndex) {
+ data[baseIndex + HEADER_TINT_INDEX] = tintIndex;
return this;
}
@@ -190,12 +247,15 @@ public final MutableQuadViewImpl tag(int tag) {
@Override
public MutableQuadViewImpl copyFrom(QuadView quad) {
final QuadViewImpl q = (QuadViewImpl) quad;
- q.computeGeometry();
System.arraycopy(q.data, q.baseIndex, data, baseIndex, EncodingFormat.TOTAL_STRIDE);
- faceNormal.set(q.faceNormal);
nominalFace = q.nominalFace;
- isGeometryInvalid = false;
+
+ isGeometryInvalid = q.isGeometryInvalid;
+
+ if (!isGeometryInvalid) {
+ faceNormal.set(q.faceNormal);
+ }
if (quad instanceof MutableQuadViewImpl mutableQuad) {
cachedSprite(mutableQuad.cachedSprite());
@@ -235,13 +295,17 @@ public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial mate
fromVanillaInternal(quad.getVertices(), 0);
data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace);
nominalFace(quad.getDirection());
- colorIndex(quad.getTintIndex());
+ tintIndex(quad.getTintIndex());
// TODO: Is this the same as hasShade?
if (!((BakedQuadView) quad).hasShade()) {
material = RenderMaterialImpl.setDisableDiffuse((RenderMaterialImpl) material, true);
}
+ if (material.ambientOcclusion().orElse(true) && !((BakedQuadView) quad).hasAO()) {
+ material = RenderMaterialImpl.setAmbientOcclusion((RenderMaterialImpl) material, TriState.FALSE);
+ }
+
material(material);
tag(0);
@@ -254,6 +318,14 @@ public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial mate
data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(headerBits, bakedView.getFlags());
isGeometryInvalid = false;
+ int lightEmission = quad.getLightEmission();
+
+ if (lightEmission > 0) {
+ for (int i = 0; i < 4; i++) {
+ lightmap(i, LightTexture.lightCoordsWithEmission(lightmap(i), lightEmission));
+ }
+ }
+
cachedSprite(quad.getSprite());
return this;
}
@@ -262,11 +334,20 @@ public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial mate
* Emit the quad without clearing the underlying data.
* Geometry is not guaranteed to be valid when called, but can be computed by calling {@link #computeGeometry()}.
*/
- public abstract void emitDirectly();
+ protected abstract void emitDirectly();
+
+ /**
+ * Apply transforms and then if transforms return true, emit the quad without clearing the underlying data.
+ */
+ public final void transformAndEmit() {
+ if (activeTransform.transform(this)) {
+ emitDirectly();
+ }
+ }
@Override
public final MutableQuadViewImpl emit() {
- emitDirectly();
+ transformAndEmit();
clear();
return this;
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java
index fce995a97b..4610108382 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java
@@ -256,8 +256,8 @@ public final RenderMaterialImpl material() {
}
@Override
- public final int colorIndex() {
- return data[baseIndex + HEADER_COLOR_INDEX];
+ public final int tintIndex() {
+ return data[baseIndex + HEADER_TINT_INDEX];
}
@Override
@@ -328,8 +328,8 @@ public int getLight(int idx) {
}
@Override
- public int getColorIndex() {
- return material().disableColorIndex() ? -1 : colorIndex();
+ public int getTintIndex() {
+ return tintIndex();
}
@Override
@@ -342,6 +342,11 @@ public Direction getLightFace() {
return lightFace();
}
+ @Override
+ public int getMaxLightQuad(int idx) {
+ return lightmap(idx);
+ }
+
@Override
public int getFlags() {
return geometryFlags();
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java
index 828e2663ae..84e20f5f0b 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java
@@ -33,6 +33,7 @@
import org.jetbrains.annotations.Nullable;
import java.util.List;
+import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@@ -58,12 +59,17 @@ public abstract class AbstractBlockRenderContext extends AbstractRenderContext {
STANDARD_MATERIALS[i] = SodiumRenderer.INSTANCE.materialFinder().ambientOcclusion(state).find();
}
}
- private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() {
+
+ public class BlockEmitter extends MutableQuadViewImpl {
{
data = new int[EncodingFormat.TOTAL_STRIDE];
clear();
}
+ public void bufferDefaultModel(BakedModel model, BlockState state, Predicate cullTest) {
+ AbstractBlockRenderContext.this.bufferDefaultModel(model, state, cullTest);
+ }
+
@Override
public void emitDirectly() {
if (type == null) {
@@ -71,10 +77,11 @@ public void emitDirectly() {
}
renderQuad(this);
}
- };
+ }
+
+
- @Deprecated
- private final BakedModelConsumerImpl vanillaModelConsumer = new BakedModelConsumerImpl();
+ private final MutableQuadViewImpl editorQuad = new BlockEmitter();
/**
* The world which the block is being rendered in.
@@ -131,7 +138,6 @@ public QuadEmitter getEmitter() {
return this.editorQuad;
}
- @Override
public boolean isFaceCulled(@Nullable Direction face) {
if (face == null || !this.enableCulling) {
return false;
@@ -153,26 +159,10 @@ public boolean isFaceCulled(@Nullable Direction face) {
}
}
- @Override
- public ItemDisplayContext itemTransformationMode() {
- throw new UnsupportedOperationException("itemTransformationMode can only be called on an item render context.");
- }
-
- @SuppressWarnings("removal")
- @Deprecated
- @Override
- public BakedModelConsumer bakedModelConsumer() {
- return this.vanillaModelConsumer;
- }
-
/**
* Pipeline entrypoint - handles transform and culling checks.
*/
private void renderQuad(MutableQuadViewImpl quad) {
- if (!this.transform(quad)) {
- return;
- }
-
if (this.isFaceCulled(quad.cullFace())) {
return;
}
@@ -217,61 +207,44 @@ protected void shadeQuad(MutableQuadViewImpl quad, LightMode lightMode, boolean
}
/* Handling of vanilla models - this is the hot path for non-modded models */
- public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) {
+ public void bufferDefaultModel(BakedModel model, @Nullable BlockState state, Predicate cullTest) {
MutableQuadViewImpl editorQuad = this.editorQuad;
// If there is no transform, we can check the culling face once for all the quads,
// and we don't need to check for transforms per-quad.
- boolean noTransform = !this.hasTransform();
for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) {
final Direction cullFace = ModelHelper.faceFromIndex(i);
+ if (cullTest.test(cullFace)) {
+ continue;
+ }
+
RandomSource random = this.randomSupplier.get();
AmbientOcclusionMode ao = PlatformBlockAccess.getInstance().usesAmbientOcclusion(model, state, modelData, type, slice, pos);
- if (noTransform) {
- if (!this.isFaceCulled(cullFace)) {
- final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, modelData);
- final int count = quads.size();
-
- for (int j = 0; j < count; j++) {
- final BakedQuad q = quads.get(j);
- editorQuad.fromVanilla(q, (type == RenderType.tripwire() || type == RenderType.translucent()) ? TRANSLUCENT_MATERIAL : STANDARD_MATERIALS[ao.ordinal()], cullFace);
- // Call processQuad instead of emit for efficiency
- // (avoid unnecessarily clearing data, trying to apply transforms, and performing cull check again)
-
- this.processQuad(editorQuad);
- }
- }
- } else {
- final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, modelData);
- final int count = quads.size();
-
- for (int j = 0; j < count; j++) {
- final BakedQuad q = quads.get(j);
- editorQuad.fromVanilla(q, (type == RenderType.tripwire() || type == RenderType.translucent()) ? TRANSLUCENT_MATERIAL : STANDARD_MATERIALS[ao.ordinal()], cullFace);
- // Call renderQuad instead of emit for efficiency
- // (avoid unnecessarily clearing data)
- this.renderQuad(editorQuad);
- }
+
+ final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, modelData);
+ final int count = quads.size();
+
+ for (int j = 0; j < count; j++) {
+ final BakedQuad q = quads.get(j);
+ editorQuad.fromVanilla(q, (type == RenderType.tripwire() || type == RenderType.translucent()) ? TRANSLUCENT_MATERIAL : STANDARD_MATERIALS[ao.ordinal()], cullFace);
+ // Call processQuad instead of emit for efficiency
+ // (avoid unnecessarily clearing data, trying to apply transforms, and performing cull check again)
+
+ this.processQuad(editorQuad);
}
}
editorQuad.clear();
}
- @SuppressWarnings("removal")
- @Deprecated
- private class BakedModelConsumerImpl implements BakedModelConsumer {
- @Override
- public void accept(BakedModel model) {
- accept(model, AbstractBlockRenderContext.this.state);
- }
+ public SodiumModelData getModelData() {
+ return modelData;
+ }
- @Override
- public void accept(BakedModel model, @Nullable BlockState state) {
- AbstractBlockRenderContext.this.bufferDefaultModel(model, state);
- }
+ public RenderType getRenderType() {
+ return type;
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java
index 6074c497cf..0f033a2c25 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java
@@ -19,69 +19,10 @@
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
-import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import java.util.function.Consumer;
-public abstract class AbstractRenderContext implements RenderContext {
- private static final QuadTransform NO_TRANSFORM = q -> true;
-
- private QuadTransform activeTransform = NO_TRANSFORM;
- private final ObjectArrayList transformStack = new ObjectArrayList<>();
- private final QuadTransform stackTransform = q -> {
- int i = transformStack.size() - 1;
-
- while (i >= 0) {
- if (!transformStack.get(i--).transform(q)) {
- return false;
- }
- }
-
- return true;
- };
-
- @Deprecated
- private final Consumer meshConsumer = mesh -> mesh.outputTo(getEmitter());
-
- protected final boolean transform(MutableQuadView q) {
- return activeTransform.transform(q);
- }
-
- @Override
- public boolean hasTransform() {
- return activeTransform != NO_TRANSFORM;
- }
-
- @Override
- public void pushTransform(QuadTransform transform) {
- if (transform == null) {
- throw new NullPointerException("Renderer received null QuadTransform.");
- }
-
- transformStack.push(transform);
-
- if (transformStack.size() == 1) {
- activeTransform = transform;
- } else if (transformStack.size() == 2) {
- activeTransform = stackTransform;
- }
- }
-
- @Override
- public void popTransform() {
- transformStack.pop();
-
- if (transformStack.isEmpty()) {
- activeTransform = NO_TRANSFORM;
- } else if (transformStack.size() == 1) {
- activeTransform = transformStack.get(0);
- }
- }
-
- // Overridden to prevent allocating a lambda every time this method is called.
- @Deprecated
- @Override
- public Consumer meshConsumer() {
- return meshConsumer;
- }
+public abstract class AbstractRenderContext {
+ abstract QuadEmitter getEmitter();
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java
index 62dfae8cad..d08d1d414f 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java
@@ -19,6 +19,7 @@
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.MatrixUtil;
+import net.caffeinemc.mods.sodium.api.util.ColorMixer;
import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper;
import net.caffeinemc.mods.sodium.client.render.frapi.mesh.EncodingFormat;
import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl;
@@ -26,28 +27,24 @@
import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil;
import net.caffeinemc.mods.sodium.mixin.features.render.frapi.ItemRendererAccessor;
import net.fabricmc.fabric.api.renderer.v1.material.BlendMode;
+import net.fabricmc.fabric.api.renderer.v1.material.GlintMode;
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
-import net.fabricmc.fabric.api.util.TriState;
import net.fabricmc.fabric.impl.renderer.VanillaModelEncoder;
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.color.item.ItemColors;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.entity.ItemRenderer;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
-import net.minecraft.world.item.BlockItem;
-import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemDisplayContext;
-import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
+import java.util.Arrays;
import java.util.function.Supplier;
/**
@@ -56,32 +53,43 @@
public class ItemRenderContext extends AbstractRenderContext {
/** Value vanilla uses for item rendering. The only sensible choice, of course. */
private static final long ITEM_RANDOM_SEED = 42L;
+ private static final int GLINT_COUNT = ItemStackRenderState.FoilType.values().length;
- private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() {
+ public class ItemEmitter extends MutableQuadViewImpl {
{
data = new int[EncodingFormat.TOTAL_STRIDE];
clear();
}
+ public void bufferDefaultModel(BakedModel model) {
+ ItemRenderContext.this.bufferDefaultModel(this, model, null);
+ }
+
+
@Override
public void emitDirectly() {
renderQuad(this);
}
- };
- @Deprecated
- private final BakedModelConsumerImpl vanillaModelConsumer = new BakedModelConsumerImpl();
+ public boolean hasTransforms() {
+ return activeTransform != NO_TRANSFORM;
+ }
+ }
+
+ private final MutableQuadViewImpl editorQuad = new ItemEmitter();
- private final ItemColors colorMap;
private final VanillaModelBufferer vanillaBufferer;
+ public ItemRenderContext(VanillaModelBufferer vanillaBufferer) {
+ this.vanillaBufferer = vanillaBufferer;
+ }
+
private final RandomSource random = new SingleThreadedRandomSource(ITEM_RANDOM_SEED);
private final Supplier randomSupplier = () -> {
random.setSeed(ITEM_RANDOM_SEED);
return random;
};
- private ItemStack itemStack;
private ItemDisplayContext transformMode;
private PoseStack poseStack;
private Matrix4f matPosition;
@@ -90,23 +98,13 @@ public void emitDirectly() {
private MultiBufferSource bufferSource;
private int lightmap;
private int overlay;
+ private int[] colors;
- private boolean isDefaultTranslucent;
- private boolean isTranslucentDirect;
- private boolean isDefaultGlint;
- private boolean isGlintDynamicDisplay;
+ private RenderType defaultLayer;
+ private ItemStackRenderState.FoilType defaultGlint;
- private PoseStack.Pose dynamicDisplayGlintEntry;
- private VertexConsumer translucentVertexConsumer;
- private VertexConsumer cutoutVertexConsumer;
- private VertexConsumer translucentGlintVertexConsumer;
- private VertexConsumer cutoutGlintVertexConsumer;
- private VertexConsumer defaultVertexConsumer;
-
- public ItemRenderContext(ItemColors colorMap, VanillaModelBufferer vanillaBufferer) {
- this.colorMap = colorMap;
- this.vanillaBufferer = vanillaBufferer;
- }
+ private PoseStack.Pose specialGlintEntry;
+ private final VertexConsumer[] vertexConsumerCache = new VertexConsumer[3 * GLINT_COUNT];
@Override
public QuadEmitter getEmitter() {
@@ -114,25 +112,7 @@ public QuadEmitter getEmitter() {
return editorQuad;
}
- @Override
- public boolean isFaceCulled(@Nullable Direction face) {
- throw new UnsupportedOperationException("isFaceCulled can only be called on a block render context.");
- }
-
- @Override
- public ItemDisplayContext itemTransformationMode() {
- return transformMode;
- }
-
- @SuppressWarnings("removal")
- @Deprecated
- @Override
- public BakedModelConsumer bakedModelConsumer() {
- return vanillaModelConsumer;
- }
-
- public void renderModel(ItemStack itemStack, ItemDisplayContext transformMode, boolean invert, PoseStack poseStack, MultiBufferSource bufferSource, int lightmap, int overlay, BakedModel model) {
- this.itemStack = itemStack;
+ public void renderModel(ItemDisplayContext transformMode, PoseStack poseStack, MultiBufferSource bufferSource, int lightmap, int overlay, BakedModel model, int[] colors, RenderType layer, ItemStackRenderState.FoilType glint) {
this.transformMode = transformMode;
this.poseStack = poseStack;
matPosition = poseStack.last().pose();
@@ -141,68 +121,39 @@ public void renderModel(ItemStack itemStack, ItemDisplayContext transformMode, b
this.bufferSource = bufferSource;
this.lightmap = lightmap;
this.overlay = overlay;
- computeOutputInfo();
+ this.colors = colors;
- ((FabricBakedModel) model).emitItemQuads(itemStack, randomSupplier, this);
+ defaultLayer = layer;
+ defaultGlint = glint;
+
+ ((FabricBakedModel) model).emitItemQuads(getEmitter(), randomSupplier);
- this.itemStack = null;
this.poseStack = null;
this.bufferSource = null;
+ this.colors = null;
- dynamicDisplayGlintEntry = null;
- translucentVertexConsumer = null;
- cutoutVertexConsumer = null;
- translucentGlintVertexConsumer = null;
- cutoutGlintVertexConsumer = null;
- defaultVertexConsumer = null;
- }
-
- private void computeOutputInfo() {
- isDefaultTranslucent = true;
- isTranslucentDirect = true;
-
- Item item = itemStack.getItem();
-
- if (item instanceof BlockItem blockItem) {
- BlockState state = blockItem.getBlock().defaultBlockState();
- RenderType renderType = ItemBlockRenderTypes.getChunkRenderType(state);
-
- if (renderType != RenderType.translucent()) {
- isDefaultTranslucent = false;
- }
-
- if (transformMode != ItemDisplayContext.GUI && !transformMode.firstPerson()) {
- isTranslucentDirect = false;
- }
- }
-
- isDefaultGlint = itemStack.hasFoil();
- isGlintDynamicDisplay = ItemRendererAccessor.sodium$hasAnimatedTexture(itemStack);
-
- defaultVertexConsumer = getVertexConsumer(BlendMode.DEFAULT, TriState.DEFAULT);
+ this.specialGlintEntry = null;
+ Arrays.fill(vertexConsumerCache, null);
}
private void renderQuad(MutableQuadViewImpl quad) {
- if (!transform(quad)) {
- return;
- }
-
final RenderMaterial mat = quad.material();
- final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex();
final boolean emissive = mat.emissive();
- final VertexConsumer vertexConsumer = getVertexConsumer(mat.blendMode(), mat.glint());
+ final VertexConsumer vertexConsumer = getVertexConsumer(mat.blendMode(), mat.glintMode());
- colorizeQuad(quad, colorIndex);
+ tintQuad(quad);
shadeQuad(quad, emissive);
bufferQuad(quad, vertexConsumer);
}
- private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) {
- if (colorIndex != -1) {
- final int itemColor = colorMap.getColor(itemStack, colorIndex);
+ private void tintQuad(MutableQuadViewImpl quad) {
+ final int tintIndex = quad.tintIndex();
+
+ if (tintIndex != -1 && tintIndex < colors.length) {
+ final int color = colors[tintIndex];
for (int i = 0; i < 4; i++) {
- quad.color(i, ColorHelper.multiplyColor(itemColor, quad.color(i)));
+ quad.color(i, ColorMixer.mulComponentWise(color, quad.color(i)));
}
}
}
@@ -231,114 +182,86 @@ private void bufferQuad(MutableQuadViewImpl quad, VertexConsumer vertexConsumer)
* in {@code RenderLayers.getEntityBlockLayer}. Layers other than
* translucent are mapped to cutout.
*/
- private VertexConsumer getVertexConsumer(BlendMode blendMode, TriState glintMode) {
- boolean translucent;
- boolean glint;
+ private VertexConsumer getVertexConsumer(BlendMode blendMode, GlintMode glintMode) {
+ RenderType type;
+ ItemStackRenderState.FoilType glint;
if (blendMode == BlendMode.DEFAULT) {
- translucent = isDefaultTranslucent;
+ type = defaultLayer;
} else {
- translucent = blendMode == BlendMode.TRANSLUCENT;
+ type = blendMode == BlendMode.TRANSLUCENT ? Sheets.translucentItemSheet() : Sheets.cutoutBlockSheet();
}
- if (glintMode == TriState.DEFAULT) {
- glint = isDefaultGlint;
+ if (glintMode == GlintMode.DEFAULT) {
+ glint = defaultGlint;
} else {
- glint = glintMode == TriState.TRUE;
+ glint = glintMode.glint;
}
- if (translucent) {
- if (glint) {
- if (translucentGlintVertexConsumer == null) {
- translucentGlintVertexConsumer = createTranslucentVertexConsumer(true);
- }
-
- return translucentGlintVertexConsumer;
- } else {
- if (translucentVertexConsumer == null) {
- translucentVertexConsumer = createTranslucentVertexConsumer(false);
- }
+ int cacheIndex;
- return translucentVertexConsumer;
- }
+ if (type == Sheets.translucentItemSheet()) {
+ cacheIndex = 0;
+ } else if (type == Sheets.cutoutBlockSheet()) {
+ cacheIndex = GLINT_COUNT;
} else {
- if (glint) {
- if (cutoutGlintVertexConsumer == null) {
- cutoutGlintVertexConsumer = createCutoutVertexConsumer(true);
- }
-
- return cutoutGlintVertexConsumer;
- } else {
- if (cutoutVertexConsumer == null) {
- cutoutVertexConsumer = createCutoutVertexConsumer(false);
- }
-
- return cutoutVertexConsumer;
- }
- }
- }
-
- private VertexConsumer createTranslucentVertexConsumer(boolean glint) {
- if (glint && isGlintDynamicDisplay) {
- return createDynamicDisplayGlintVertexConsumer(Minecraft.useShaderTransparency() && !isTranslucentDirect ? Sheets.translucentItemSheet() : Sheets.translucentCullBlockSheet());
+ cacheIndex = 2 * GLINT_COUNT;
}
- if (isTranslucentDirect) {
- return ItemRenderer.getFoilBufferDirect(bufferSource, Sheets.translucentCullBlockSheet(), true, glint);
- } else if (Minecraft.useShaderTransparency()) {
- return ItemRenderer.getFoilBuffer(bufferSource, Sheets.translucentItemSheet(), true, glint);
- } else {
- return ItemRenderer.getFoilBuffer(bufferSource, Sheets.translucentItemSheet(), true, glint);
- }
- }
+ cacheIndex += glint.ordinal();
+ VertexConsumer vertexConsumer = vertexConsumerCache[cacheIndex];
- private VertexConsumer createCutoutVertexConsumer(boolean glint) {
- if (glint && isGlintDynamicDisplay) {
- return createDynamicDisplayGlintVertexConsumer(Sheets.cutoutBlockSheet());
+ if (vertexConsumer == null) {
+ vertexConsumer = createVertexConsumer(type, glint);
+ vertexConsumerCache[cacheIndex] = vertexConsumer;
}
- return ItemRenderer.getFoilBufferDirect(bufferSource, Sheets.cutoutBlockSheet(), true, glint);
+ return vertexConsumer;
}
- private VertexConsumer createDynamicDisplayGlintVertexConsumer(RenderType type) {
- if (dynamicDisplayGlintEntry == null) {
- dynamicDisplayGlintEntry = poseStack.last().copy();
+ private VertexConsumer createVertexConsumer(RenderType type, ItemStackRenderState.FoilType glint) {
+ if (glint == ItemStackRenderState.FoilType.SPECIAL) {
+ if (specialGlintEntry == null) {
+ specialGlintEntry = poseStack.last().copy();
- if (transformMode == ItemDisplayContext.GUI) {
- MatrixUtil.mulComponentWise(dynamicDisplayGlintEntry.pose(), 0.5F);
- } else if (transformMode.firstPerson()) {
- MatrixUtil.mulComponentWise(dynamicDisplayGlintEntry.pose(), 0.75F);
+ if (transformMode == ItemDisplayContext.GUI) {
+ MatrixUtil.mulComponentWise(specialGlintEntry.pose(), 0.5F);
+ } else if (transformMode.firstPerson()) {
+ MatrixUtil.mulComponentWise(specialGlintEntry.pose(), 0.75F);
+ }
}
+
+ return ItemRendererAccessor.sodium$getCompassFoilBuffer(bufferSource, type, specialGlintEntry);
}
- return ItemRenderer.getCompassFoilBuffer(bufferSource, type, dynamicDisplayGlintEntry);
+ return ItemRenderer.getFoilBuffer(bufferSource, type, true, glint != ItemStackRenderState.FoilType.NONE);
}
- public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) {
- if (hasTransform() || vanillaBufferer == null) {
- VanillaModelEncoder.emitItemQuads(model, state, randomSupplier, ItemRenderContext.this);
+ public void bufferDefaultModel(QuadEmitter quadEmitter, BakedModel model, @Nullable BlockState state) {
+ if (vanillaBufferer == null) {
+ VanillaModelEncoder.emitItemQuads(quadEmitter, model, null, randomSupplier);
} else {
- vanillaBufferer.accept(model, itemStack, lightmap, overlay, poseStack, defaultVertexConsumer);
- }
- }
+ VertexConsumer vertexConsumer;
+ if (defaultGlint == ItemStackRenderState.FoilType.SPECIAL) {
+ PoseStack.Pose pose = poseStack.last().copy();
+ if (transformMode == ItemDisplayContext.GUI) {
+ MatrixUtil.mulComponentWise(pose.pose(), 0.5F);
+ } else if (transformMode.firstPerson()) {
+ MatrixUtil.mulComponentWise(pose.pose(), 0.75F);
+ }
- @SuppressWarnings("removal")
- @Deprecated
- private class BakedModelConsumerImpl implements BakedModelConsumer {
- @Override
- public void accept(BakedModel model) {
- accept(model, null);
- }
+ vertexConsumer = ItemRendererAccessor.sodium$getCompassFoilBuffer(bufferSource, defaultLayer, pose);
+ } else {
+ vertexConsumer = ItemRenderer.getFoilBuffer(bufferSource, defaultLayer, true, defaultGlint != ItemStackRenderState.FoilType.NONE);
+ }
- @Override
- public void accept(BakedModel model, @Nullable BlockState state) {
- bufferDefaultModel(model, state);
+ vanillaBufferer.accept(model, colors, lightmap, overlay, poseStack, vertexConsumer);
}
}
/** used to accept a method reference from the ItemRenderer. */
@FunctionalInterface
public interface VanillaModelBufferer {
- void accept(BakedModel model, ItemStack stack, int color, int overlay, PoseStack matrixStack, VertexConsumer buffer);
+ void accept(BakedModel model, int[] colirs, int color, int overlay, PoseStack matrixStack, VertexConsumer buffer);
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java
index 130d240b2e..7c0a26cf11 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java
@@ -18,10 +18,11 @@
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
+import net.caffeinemc.mods.sodium.api.util.ColorARGB;
+import net.caffeinemc.mods.sodium.api.util.ColorMixer;
import net.caffeinemc.mods.sodium.client.model.light.LightMode;
import net.caffeinemc.mods.sodium.client.model.light.LightPipelineProvider;
import net.caffeinemc.mods.sodium.client.model.light.data.SingleBlockLightDataCache;
-import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper;
import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl;
import net.caffeinemc.mods.sodium.client.render.texture.SpriteFinderCache;
import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil;
@@ -75,7 +76,7 @@ public void renderModel(BlockAndTintGetter blockView, BakedModel model, BlockSta
this.prepareCulling(cull);
this.prepareAoInfo(model.useAmbientOcclusion());
- ((FabricBakedModel) model).emitBlockQuads(blockView, state, pos, this.randomSupplier, this);
+ ((FabricBakedModel) model).emitBlockQuads(getEmitter(), blockView, state, pos, this.randomSupplier, this::isFaceCulled);
this.level = null;
this.type = null;
@@ -88,7 +89,6 @@ public void renderModel(BlockAndTintGetter blockView, BakedModel model, BlockSta
@Override
protected void processQuad(MutableQuadViewImpl quad) {
final RenderMaterial mat = quad.material();
- final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex();
final TriState aoMode = mat.ambientOcclusion();
final ShadeMode shadeMode = mat.shadeMode();
final LightMode lightMode;
@@ -99,17 +99,17 @@ protected void processQuad(MutableQuadViewImpl quad) {
}
final boolean emissive = mat.emissive();
- colorizeQuad(quad, colorIndex);
+ tintQuad(quad);
shadeQuad(quad, lightMode, emissive, shadeMode);
bufferQuad(quad);
}
- private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) {
- if (colorIndex != -1) {
- final int blockColor = 0xFF000000 | this.colorMap.getColor(this.state, this.level, this.pos, colorIndex);
+ private void tintQuad(MutableQuadViewImpl quad) {
+ if (quad.tintIndex() != -1) {
+ final int blockColor = 0xFF000000 | this.colorMap.getColor(this.state, this.level, this.pos, quad.tintIndex());
for (int i = 0; i < 4; i++) {
- quad.color(i, ColorHelper.multiplyColor(blockColor, quad.color(i)));
+ quad.color(i, ColorMixer.mulComponentWise(blockColor, quad.color(i)));
}
}
}
@@ -121,7 +121,7 @@ protected void shadeQuad(MutableQuadViewImpl quad, LightMode lightMode, boolean
float[] brightnesses = this.quadLightData.br;
for (int i = 0; i < 4; i++) {
- quad.color(i, ColorHelper.multiplyRGB(quad.color(i), brightnesses[i]));
+ quad.color(i, ColorARGB.mulRGB(quad.color(i), brightnesses[i]));
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java
index e0e6cf6088..16cf9cf093 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java
@@ -1,184 +1,237 @@
package net.caffeinemc.mods.sodium.client.render.immediate;
+import com.mojang.blaze3d.buffers.BufferUsage;
+import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
-import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex;
-import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter;
import net.caffeinemc.mods.sodium.api.util.ColorABGR;
import net.caffeinemc.mods.sodium.api.util.ColorARGB;
-import net.caffeinemc.mods.sodium.api.util.ColorMixer;
+import net.caffeinemc.mods.sodium.api.util.ColorU8;
+import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter;
+import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex;
import net.minecraft.client.Camera;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
-import net.minecraft.client.renderer.FogRenderer;
-import net.minecraft.client.renderer.ShaderInstance;
+import net.minecraft.client.renderer.*;
import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.packs.resources.Resource;
-import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceProvider;
+import net.minecraft.util.ARGB;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
+import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
-import org.joml.Vector3f;
+import org.joml.Vector4f;
import org.lwjgl.opengl.GL32C;
import org.lwjgl.system.MemoryStack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.Objects;
public class CloudRenderer {
- private static final ResourceLocation CLOUDS_TEXTURE_ID = ResourceLocation.withDefaultNamespace("textures/environment/clouds.png");
-
- private CloudTextureData textureData;
- private ShaderInstance shaderProgram;
-
- private @Nullable CloudRenderer.CloudGeometry cachedGeometry;
+ private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-CloudRenderer");
+
+ private static final ShaderProgram CLOUDS_SHADER = new ShaderProgram(
+ ResourceLocation.fromNamespaceAndPath("sodium", "clouds"),
+ DefaultVertexFormat.POSITION_COLOR,
+ ShaderDefines.builder()
+ .build()
+ );
+
+ private static final ResourceLocation CLOUDS_TEXTURE_ID =
+ ResourceLocation.withDefaultNamespace("textures/environment/clouds.png");
+
+ private static final float CLOUD_HEIGHT = 4.0f; // The height of the cloud cells
+ private static final float CLOUD_WIDTH = 12.0f; // The width/length of cloud cells
+
+ // Bitmasks for each cloud face
+ private static final int FACE_MASK_NEG_Y = 1 << 0;
+ private static final int FACE_MASK_POS_Y = 1 << 1;
+ private static final int FACE_MASK_NEG_X = 1 << 2;
+ private static final int FACE_MASK_POS_X = 1 << 3;
+ private static final int FACE_MASK_NEG_Z = 1 << 4;
+ private static final int FACE_MASK_POS_Z = 1 << 5;
+
+ // The brightness of each fac
+ // The final color of each vertex is: vec4((texel.rgb * brightness), texel.a * 0.8)
+ private static final int BRIGHTNESS_POS_Y = ColorU8.normalizedFloatToByte(1.0F); // used for +Y
+ private static final int BRIGHTNESS_NEG_Y = ColorU8.normalizedFloatToByte(0.7F); // used for -Y
+ private static final int BRIGHTNESS_X_AXIS = ColorU8.normalizedFloatToByte(0.9F); // used for -X and +X
+ private static final int BRIGHTNESS_Z_AXIS = ColorU8.normalizedFloatToByte(0.8F); // used for -Z and +Z
+
+ private @Nullable CloudTextureData textureData;
+ private @Nullable CloudGeometry builtGeometry;
public CloudRenderer(ResourceProvider resourceProvider) {
- this.reloadTextures(resourceProvider);
+ this.reload(resourceProvider);
}
public void render(Camera camera,
ClientLevel level,
Matrix4f projectionMatrix,
- PoseStack poseStack,
+ Matrix4f modelView,
float ticks,
- float tickDelta)
+ float tickDelta,
+ int color)
{
- float cloudHeight = level.effects().getCloudHeight();
+ float height = level.effects()
+ .getCloudHeight() + 0.33f; // arithmetic against NaN always produces NaN
// Vanilla uses NaN height as a way to disable cloud rendering
- if (Float.isNaN(cloudHeight)) {
+ if (Float.isNaN(height)) {
return;
}
- // Skip rendering clouds if texture is completely blank
- if (this.textureData.isBlank) {
+ // Skip rendering clouds if the texture data isn't available
+ // This can happen if the texture failed to load, or if the texture is completely empty
+ if (this.textureData == null) {
return;
}
- Vec3 pos = camera.getPosition();
+ Vec3 cameraPos = camera.getPosition();
+ int renderDistance = getCloudRenderDistance();
+ var renderMode = Minecraft.getInstance().options.getCloudsType();
+
+ // Translation of the clouds texture in world-space
+ double worldX = (cameraPos.x + ((ticks + tickDelta) * 0.03D));
+ double worldZ = (cameraPos.z + 3.96D);
+
+ double textureWidth = this.textureData.width * CLOUD_WIDTH;
+ double textureHeight = this.textureData.height * CLOUD_WIDTH;
+ worldX -= Mth.floor(worldX / textureWidth) * textureWidth;
+ worldZ -= Mth.floor(worldZ / textureHeight) * textureHeight;
- double cloudTime = (ticks + tickDelta) * 0.03F;
- double cloudCenterX = (pos.x() + cloudTime);
- double cloudCenterZ = (pos.z()) + 0.33D;
+ // The coordinates of the cloud cell which the camera is within
+ int cellX = Mth.floor(worldX / CLOUD_WIDTH);
+ int cellZ = Mth.floor(worldZ / CLOUD_WIDTH);
- int cloudDistance = getCloudRenderDistance();
+ // The orientation of the camera relative to the clouds
+ // This is used to cull back-facing geometry
+ ViewOrientation orientation;
- int centerCellX = (int) (Math.floor(cloudCenterX / 12.0));
- int centerCellZ = (int) (Math.floor(cloudCenterZ / 12.0));
+ if (renderMode == CloudStatus.FANCY) {
+ orientation = ViewOrientation.getOrientation(cameraPos, height, height + CLOUD_HEIGHT);
+ } else {
+ // When fast clouds are used, there is no orientation of faces, since culling is disabled.
+ // To avoid unnecessary rebuilds, simply mark a null (undefined) orientation.
+ orientation = null;
+ }
- // -1 if below clouds, +1 if above clouds
- var cloudType = Minecraft.getInstance().options.getCloudsType();
- int orientation = cloudType == CloudStatus.FANCY ? (int) Math.signum(pos.y() - cloudHeight) : 0;
- var parameters = new CloudGeometryParameters(centerCellX, centerCellZ, cloudDistance, orientation, cloudType);
+ var parameters = new CloudGeometryParameters(cellX, cellZ, renderDistance, orientation, renderMode);
- CloudGeometry geometry = this.cachedGeometry;
+ CloudGeometry geometry = this.builtGeometry;
+ // Re-generate the cached cloud geometry if necessary
if (geometry == null || !Objects.equals(geometry.params(), parameters)) {
- this.cachedGeometry = (geometry = rebuildGeometry(geometry, parameters, this.textureData));
+ this.builtGeometry = (geometry = rebuildGeometry(geometry, parameters, this.textureData));
}
VertexBuffer vertexBuffer = geometry.vertexBuffer();
+
+ // The vertex buffer can be empty when there are no clouds to render
if (vertexBuffer == null) {
return;
}
- final float translateX = (float) (cloudCenterX - (centerCellX * 12));
- final float translateZ = (float) (cloudCenterZ - (centerCellZ * 12));
-
- poseStack.pushPose();
+ // Apply world->view transform
+ final float viewPosX = (float) (worldX - (cellX * CLOUD_WIDTH));
+ final float viewPosY = (float) cameraPos.y() - height;
+ final float viewPosZ = (float) (worldZ - (cellZ * CLOUD_WIDTH));
- var poseEntry = poseStack.last();
+ Matrix4f modelViewMatrix = new Matrix4f(modelView);
+ modelViewMatrix.translate(-viewPosX, -viewPosY, -viewPosZ);
- Matrix4f modelViewMatrix = poseEntry.pose();
- modelViewMatrix.translate(-translateX, cloudHeight - (float) pos.y() + 0.33F, -translateZ);
+ // State setup
+ final var prevFogParameters = copyShaderFogParameters(RenderSystem.getShaderFog());
+ final var flat = geometry.params()
+ .renderMode() == CloudStatus.FAST;
- final var prevShaderFogShape = RenderSystem.getShaderFogShape();
- final var prevShaderFogEnd = RenderSystem.getShaderFogEnd();
- final var prevShaderFogStart = RenderSystem.getShaderFogStart();
+ FogParameters fogParameters = FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, new Vector4f(prevFogParameters.red(), prevFogParameters.green(), prevFogParameters.blue(), prevFogParameters.alpha()), renderDistance * 8, shouldUseWorldFog(level, cameraPos), tickDelta);
- FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, cloudDistance * 8, shouldUseWorldFog(level, pos), tickDelta);
+ RenderSystem.setShaderColor(ARGB.redFloat(color), ARGB.greenFloat(color), ARGB.blueFloat(color), 0.8F);
+ RenderSystem.setShaderFog(fogParameters);
- boolean fastClouds = geometry.params().renderMode() == CloudStatus.FAST;
- boolean fabulous = Minecraft.useShaderTransparency();
+ RenderTarget renderTarget = Minecraft.getInstance().levelRenderer.getCloudsTarget();
- if (fastClouds) {
- RenderSystem.disableCull();
+ if (renderTarget != null) {
+ renderTarget.bindWrite(false);
+ } else {
+ Minecraft.getInstance()
+ .getMainRenderTarget()
+ .bindWrite(false);
}
- if (fabulous) {
- Minecraft.getInstance().levelRenderer.getCloudsTarget().bindWrite(false);
- }
+ RenderSystem.enableBlend();
+ RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
- Vec3 colorModulator = level.getCloudColor(tickDelta);
- RenderSystem.setShaderColor((float) colorModulator.x, (float) colorModulator.y, (float) colorModulator.z, 0.8f);
+ RenderSystem.setShader(CLOUDS_SHADER);
- vertexBuffer.bind();
+ if (flat) {
+ RenderSystem.disableCull();
+ }
- RenderSystem.enableBlend();
RenderSystem.enableDepthTest();
- RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA,
- GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
RenderSystem.depthFunc(GL32C.GL_LESS);
- vertexBuffer.drawWithShader(modelViewMatrix, projectionMatrix, this.shaderProgram);
+ // Draw
+ vertexBuffer.bind();
+ vertexBuffer.drawWithShader(modelViewMatrix, projectionMatrix, RenderSystem.getShader());
+ VertexBuffer.unbind();
+ // State teardown
RenderSystem.depthFunc(GL32C.GL_LEQUAL);
- RenderSystem.disableBlend();
+ RenderSystem.disableDepthTest();
- VertexBuffer.unbind();
-
- if (fastClouds) {
+ if (flat) {
RenderSystem.enableCull();
}
- if (fabulous) {
- Minecraft.getInstance().getMainRenderTarget().bindWrite(false);
+ RenderSystem.defaultBlendFunc();
+ RenderSystem.disableBlend();
+
+ if (renderTarget != null) {
+ Minecraft.getInstance()
+ .getMainRenderTarget()
+ .bindWrite(false);
}
+ RenderSystem.setShaderFog(prevFogParameters);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
-
- RenderSystem.setShaderFogShape(prevShaderFogShape);
- RenderSystem.setShaderFogEnd(prevShaderFogEnd);
- RenderSystem.setShaderFogStart(prevShaderFogStart);
-
- poseStack.popPose();
}
private static @NotNull CloudGeometry rebuildGeometry(@Nullable CloudGeometry existingGeometry,
CloudGeometryParameters parameters,
CloudTextureData textureData)
{
- BufferBuilder bufferBuilder = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
+ BufferBuilder bufferBuilder = Tesselator.getInstance()
+ .begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
var writer = VertexBufferWriter.of(bufferBuilder);
- var originCellX = parameters.originX();
- var originCellZ = parameters.originZ();
-
- var orientation = parameters.orientation();
+ final var radius = parameters.radius();
+ final var orientation = parameters.orientation();
+ final var flat = parameters.renderMode() == CloudStatus.FAST;
- var radius = parameters.radius();
- var useFastGraphics = parameters.renderMode() == CloudStatus.FAST;
+ final var slice = textureData.slice(parameters.originX(), parameters.originZ(), radius);
- addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, 0, 0, orientation, useFastGraphics);
+ // Iterate from the center coordinates (0, 0) outwards
+ // Since the geometry will be in sorted order, this avoids needing a depth pre-pass
+ addCellGeometryToBuffer(writer, slice, 0, 0, orientation, flat);
for (int layer = 1; layer <= radius; layer++) {
for (int z = -layer; z < layer; z++) {
int x = Math.abs(z) - layer;
- addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics);
+ addCellGeometryToBuffer(writer, slice, x, z, orientation, flat);
}
for (int z = layer; z > -layer; z--) {
int x = layer - Math.abs(z);
- addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics);
+ addCellGeometryToBuffer(writer, slice, x, z, orientation, flat);
}
}
@@ -187,38 +240,43 @@ public void render(Camera camera,
for (int z = -radius; z <= -l; z++) {
int x = -z - layer;
- addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics);
+ addCellGeometryToBuffer(writer, slice, x, z, orientation, flat);
}
for (int z = l; z <= radius; z++) {
int x = z - layer;
- addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics);
+ addCellGeometryToBuffer(writer, slice, x, z, orientation, flat);
}
for (int z = radius; z >= l; z--) {
int x = layer - z;
- addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics);
+ addCellGeometryToBuffer(writer, slice, x, z, orientation, flat);
}
for (int z = -l; z >= -radius; z--) {
int x = layer + z;
- addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics);
+ addCellGeometryToBuffer(writer, slice, x, z, orientation, flat);
}
}
- MeshData builtBuffer = bufferBuilder.build();
+ @Nullable MeshData meshData = bufferBuilder.build();
+ @Nullable VertexBuffer vertexBuffer = null;
- VertexBuffer vertexBuffer = null;
+ if (existingGeometry != null) {
+ vertexBuffer = existingGeometry.vertexBuffer();
+ }
- if (builtBuffer != null) {
- if (existingGeometry != null) {
- vertexBuffer = existingGeometry.vertexBuffer();
- }
+ if (meshData != null) {
if (vertexBuffer == null) {
- vertexBuffer = new VertexBuffer(VertexBuffer.Usage.DYNAMIC);
+ vertexBuffer = new VertexBuffer(BufferUsage.DYNAMIC_WRITE);
}
- uploadToVertexBuffer(vertexBuffer, builtBuffer);
+ uploadToVertexBuffer(vertexBuffer, meshData);
+ } else {
+ if (vertexBuffer != null) {
+ vertexBuffer.close();
+ vertexBuffer = null;
+ }
}
Tesselator.getInstance().clear();
@@ -227,172 +285,235 @@ public void render(Camera camera,
}
private static void addCellGeometryToBuffer(VertexBufferWriter writer,
- CloudTextureData textureData,
- int originX,
- int originZ,
- int offsetX,
- int offsetZ,
- int orientation,
- boolean useFastGraphics) {
- int cellX = originX + offsetX;
- int cellZ = originZ + offsetZ;
-
- int cellIndex = textureData.getCellIndexWrapping(cellX, cellZ);
- int cellFaces = textureData.getCellFaces(cellIndex) & getVisibleFaces(offsetX, offsetZ, orientation);
-
- if (cellFaces == 0) {
+ CloudTextureData.Slice textureData,
+ int x,
+ int z,
+ @Nullable CloudRenderer.ViewOrientation orientation,
+ boolean flat)
+ {
+ int index = textureData.getCellIndex(x, z);
+ int faces = textureData.getCellFaces(index) & getVisibleFaces(x, z, orientation);
+
+ if (faces == 0) {
return;
}
- int cellColor = textureData.getCellColor(cellIndex);
+ int color = textureData.getCellColor(index);
- if (ColorABGR.unpackAlpha(cellColor) == 0) {
+ if (isTransparent(color)) {
return;
}
- float x = offsetX * 12;
- float z = offsetZ * 12;
-
- if (useFastGraphics) {
- emitCellGeometry2D(writer, cellFaces, cellColor, x, z);
+ if (flat) {
+ emitCellGeometryFlat(writer, color, x, z);
} else {
- emitCellGeometry3D(writer, cellFaces, cellColor, x, z, false);
+ emitCellGeometryExterior(writer, faces, color, x, z);
- int distance = Math.abs(offsetX) + Math.abs(offsetZ);
-
- if (distance <= 1) {
- emitCellGeometry3D(writer, CloudFaceSet.all(), cellColor, x, z, true);
+ if (taxicabDistance(x, z) <= 1) {
+ emitCellGeometryInterior(writer, color, x, z);
}
}
}
- private static int getVisibleFaces(int x, int z, int orientation) {
- int faces = CloudFaceSet.all();
+ private static int getVisibleFaces(int x, int z, ViewOrientation orientation) {
+ int faces = 0;
- if (x > 0) {
- faces = CloudFaceSet.remove(faces, CloudFace.POS_X);
+ if (x <= 0) {
+ faces |= FACE_MASK_POS_X;
}
- if (z > 0) {
- faces = CloudFaceSet.remove(faces, CloudFace.POS_Z);
+ if (z <= 0) {
+ faces |= FACE_MASK_POS_Z;
}
- if (x < 0) {
- faces = CloudFaceSet.remove(faces, CloudFace.NEG_X);
+ if (x >= 0) {
+ faces |= FACE_MASK_NEG_X;
}
- if (z < 0) {
- faces = CloudFaceSet.remove(faces, CloudFace.NEG_Z);
+ if (z >= 0) {
+ faces |= FACE_MASK_NEG_Z;
}
- if (orientation < 0) {
- faces = CloudFaceSet.remove(faces, CloudFace.POS_Y);
+ if (orientation != ViewOrientation.BELOW_CLOUDS) {
+ faces |= FACE_MASK_POS_Y;
}
- if (orientation > 0) {
- faces = CloudFaceSet.remove(faces, CloudFace.NEG_Y);
+ if (orientation != ViewOrientation.ABOVE_CLOUDS) {
+ faces |= FACE_MASK_NEG_Y;
}
return faces;
}
- private static final Vector3f[][] VERTICES = new Vector3f[CloudFace.COUNT][];
-
- static {
- VERTICES[CloudFace.NEG_Y.ordinal()] = new Vector3f[] {
- new Vector3f(12.0f, 0.0f, 12.0f),
- new Vector3f( 0.0f, 0.0f, 12.0f),
- new Vector3f( 0.0f, 0.0f, 0.0f),
- new Vector3f(12.0f, 0.0f, 0.0f)
- };
-
- VERTICES[CloudFace.POS_Y.ordinal()] = new Vector3f[] {
- new Vector3f( 0.0f, 4.0f, 12.0f),
- new Vector3f(12.0f, 4.0f, 12.0f),
- new Vector3f(12.0f, 4.0f, 0.0f),
- new Vector3f( 0.0f, 4.0f, 0.0f)
- };
-
- VERTICES[CloudFace.NEG_X.ordinal()] = new Vector3f[] {
- new Vector3f( 0.0f, 0.0f, 12.0f),
- new Vector3f( 0.0f, 4.0f, 12.0f),
- new Vector3f( 0.0f, 4.0f, 0.0f),
- new Vector3f( 0.0f, 0.0f, 0.0f)
- };
-
- VERTICES[CloudFace.POS_X.ordinal()] = new Vector3f[] {
- new Vector3f(12.0f, 4.0f, 12.0f),
- new Vector3f(12.0f, 0.0f, 12.0f),
- new Vector3f(12.0f, 0.0f, 0.0f),
- new Vector3f(12.0f, 4.0f, 0.0f)
- };
-
- VERTICES[CloudFace.NEG_Z.ordinal()] = new Vector3f[] {
- new Vector3f(12.0f, 4.0f, 0.0f),
- new Vector3f(12.0f, 0.0f, 0.0f),
- new Vector3f( 0.0f, 0.0f, 0.0f),
- new Vector3f( 0.0f, 4.0f, 0.0f)
- };
-
- VERTICES[CloudFace.POS_Z.ordinal()] = new Vector3f[] {
- new Vector3f(12.0f, 0.0f, 12.0f),
- new Vector3f(12.0f, 4.0f, 12.0f),
- new Vector3f( 0.0f, 4.0f, 12.0f),
- new Vector3f( 0.0f, 0.0f, 12.0f)
- };
- }
-
- private static void emitCellGeometry2D(VertexBufferWriter writer, int faces, int color, float x, float z) {
+ private static void emitCellGeometryFlat(VertexBufferWriter writer, int texel, int x, int z) {
try (MemoryStack stack = MemoryStack.stackPush()) {
- final long buffer = stack.nmalloc(4 * ColorVertex.STRIDE);
-
- long ptr = buffer;
- int count = 0;
+ final long vertexBuffer = stack.nmalloc(4 * ColorVertex.STRIDE);
+ long ptr = vertexBuffer;
- int mixedColor = ColorMixer.mul(color, CloudFace.POS_Y.getColor());
+ final float x0 = (x * CLOUD_WIDTH);
+ final float x1 = x0 + CLOUD_WIDTH;
+ final float z0 = (z * CLOUD_WIDTH);
+ final float z1 = z0 + CLOUD_WIDTH;
- ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 12.0f, mixedColor);
- ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 12.0f, mixedColor);
- ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 0.0f, mixedColor);
- ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 0.0f, mixedColor);
-
- count += 4;
+ {
+ final int color = ColorABGR.mulRGB(texel, BRIGHTNESS_POS_Y);
+ ptr = writeVertex(ptr, x1, 0.0f, z1, color);
+ ptr = writeVertex(ptr, x0, 0.0f, z1, color);
+ ptr = writeVertex(ptr, x0, 0.0f, z0, color);
+ ptr = writeVertex(ptr, x1, 0.0f, z0, color);
+ }
- writer.push(stack, buffer, count, ColorVertex.FORMAT);
+ writer.push(stack, vertexBuffer, 4, ColorVertex.FORMAT);
}
}
- private static void emitCellGeometry3D(VertexBufferWriter writer, int visibleFaces, int baseColor, float posX, float posZ, boolean interior) {
+ private static void emitCellGeometryExterior(VertexBufferWriter writer, int cellFaces, int cellColor, int cellX, int cellZ) {
try (MemoryStack stack = MemoryStack.stackPush()) {
- final long buffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE);
+ final long vertexBuffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE);
+ int vertexCount = 0;
+
+ long ptr = vertexBuffer;
+
+ final float x0 = cellX * CLOUD_WIDTH;
+ final float y0 = 0.0f;
+ final float z0 = cellZ * CLOUD_WIDTH;
+
+ final float x1 = x0 + CLOUD_WIDTH;
+ final float y1 = y0 + CLOUD_HEIGHT;
+ final float z1 = z0 + CLOUD_WIDTH;
+
+ // -Y
+ if ((cellFaces & FACE_MASK_NEG_Y) != 0) {
+ final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_NEG_Y);
+ ptr = writeVertex(ptr, x1, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z0, vertexColor);
+ vertexCount += 4;
+ }
+
+ // +Y
+ if ((cellFaces & FACE_MASK_POS_Y) != 0) {
+ final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_POS_Y);
+ ptr = writeVertex(ptr, x0, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z0, vertexColor);
+ vertexCount += 4;
+ }
- long ptr = buffer;
- int count = 0;
+ if ((cellFaces & (FACE_MASK_NEG_X | FACE_MASK_POS_X)) != 0) {
+ final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_X_AXIS);
- for (var face : CloudFace.VALUES) {
- if (!CloudFaceSet.contains(visibleFaces, face)) {
- continue;
+ // -X
+ if ((cellFaces & FACE_MASK_NEG_X) != 0) {
+ ptr = writeVertex(ptr, x0, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z0, vertexColor);
+ vertexCount += 4;
}
- final var vertices = VERTICES[face.ordinal()];
- final int color = ColorMixer.mul(baseColor, face.getColor());
+ // +X
+ if ((cellFaces & FACE_MASK_POS_X) != 0) {
+ ptr = writeVertex(ptr, x1, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z0, vertexColor);
+ vertexCount += 4;
+ }
+ }
- for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) {
- Vector3f vertex = vertices[interior ? 3 - vertexIndex : vertexIndex];
+ if ((cellFaces & (FACE_MASK_NEG_Z | FACE_MASK_POS_Z)) != 0) {
+ final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_Z_AXIS);
- final float x = vertex.x + posX;
- final float y = vertex.y;
- final float z = vertex.z + posZ;
+ // -Z
+ if ((cellFaces & FACE_MASK_NEG_Z) != 0) {
+ ptr = writeVertex(ptr, x1, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z0, vertexColor);
+ vertexCount += 4;
+ }
- ptr = writeVertex(ptr, x, y, z, color);
+ // +Z
+ if ((cellFaces & FACE_MASK_POS_Z) != 0) {
+ ptr = writeVertex(ptr, x1, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z1, vertexColor);
+ vertexCount += 4;
}
+ }
+
+ writer.push(stack, vertexBuffer, vertexCount, ColorVertex.FORMAT);
+ }
+ }
+
+ private static void emitCellGeometryInterior(VertexBufferWriter writer, int baseColor, int cellX, int cellZ) {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final long vertexBuffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE);
+ long ptr = vertexBuffer;
+
+ final float x0 = cellX * CLOUD_WIDTH;
+ final float y0 = 0.0f;
+ final float z0 = cellZ * CLOUD_WIDTH;
+
+ final float x1 = x0 + CLOUD_WIDTH;
+ final float y1 = y0 + CLOUD_HEIGHT;
+ final float z1 = z0 + CLOUD_WIDTH;
+
+ {
+ // -Y
+ final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_NEG_Y);
+ ptr = writeVertex(ptr, x1, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z1, vertexColor);
+ }
+
+ {
+ // +Y
+ final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_POS_Y);
+ ptr = writeVertex(ptr, x0, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z1, vertexColor);
+ }
- count += 4;
+ {
+ final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_X_AXIS);
+
+ // -X
+ ptr = writeVertex(ptr, x0, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z1, vertexColor);
+
+ // +X
+ ptr = writeVertex(ptr, x1, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z1, vertexColor);
}
- if (count > 0) {
- writer.push(stack, buffer, count, ColorVertex.FORMAT);
+ {
+ final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_Z_AXIS);
+
+ // -Z
+ ptr = writeVertex(ptr, x0, y1, z0, vertexColor);
+ ptr = writeVertex(ptr, x0, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z0, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z0, vertexColor);
+
+ // +Z
+ ptr = writeVertex(ptr, x0, y0, z1, vertexColor);
+ ptr = writeVertex(ptr, x0, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y1, z1, vertexColor);
+ ptr = writeVertex(ptr, x1, y0, z1, vertexColor);
}
+
+ writer.push(stack, vertexBuffer, 6 * 4, ColorVertex.FORMAT);
}
}
@@ -408,43 +529,36 @@ private static void uploadToVertexBuffer(VertexBuffer vertexBuffer, MeshData bui
VertexBuffer.unbind();
}
- public void reloadTextures(ResourceProvider resourceProvider) {
+ public void reload(ResourceProvider resourceProvider) {
this.destroy();
-
- this.textureData = loadTextureData();
-
- try {
- this.shaderProgram = new ShaderInstance(resourceProvider, "clouds", DefaultVertexFormat.POSITION_COLOR);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ this.textureData = loadTextureData(resourceProvider);
}
public void destroy() {
- if (this.shaderProgram != null) {
- this.shaderProgram.close();
- this.shaderProgram = null;
- }
+ if (this.builtGeometry != null) {
+ var vertexBuffer = this.builtGeometry.vertexBuffer();
- if (this.cachedGeometry != null) {
- var vertexBuffer = this.cachedGeometry.vertexBuffer();
- vertexBuffer.close();
+ if (vertexBuffer != null) {
+ vertexBuffer.close();
+ }
- this.cachedGeometry = null;
+ this.builtGeometry = null;
}
}
- private static CloudTextureData loadTextureData() {
- ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
- Resource resource = resourceManager.getResource(CLOUDS_TEXTURE_ID)
- .orElseThrow();
+ private static @Nullable CloudTextureData loadTextureData(ResourceProvider resourceProvider) {
+ var resource = resourceProvider.getResource(CloudRenderer.CLOUDS_TEXTURE_ID)
+ .orElseThrow(); // always provided by default resource pack
- try (InputStream inputStream = resource.open()){
- try (NativeImage nativeImage = NativeImage.read(inputStream)) {
- return new CloudTextureData(nativeImage);
- }
- } catch (IOException ex) {
- throw new RuntimeException("Failed to load texture data", ex);
+ try (var inputStream = resource.open();
+ var nativeImage = NativeImage.read(inputStream))
+ {
+ return CloudTextureData.load(nativeImage);
+ }
+ catch (Throwable t) {
+ LOGGER.error("Failed to load texture '{}'. The rendering of clouds in the skybox will be disabled. " +
+ "This may be caused by an incompatible resource pack.", CloudRenderer.CLOUDS_TEXTURE_ID, t);
+ return null;
}
}
@@ -457,96 +571,107 @@ private static int getCloudRenderDistance() {
return Math.max(32, (Minecraft.getInstance().options.getEffectiveRenderDistance() * 2) + 9);
}
- private enum CloudFace {
- NEG_Y(ColorABGR.pack(0.7F, 0.7F, 0.7F, 1.0f)),
- POS_Y(ColorABGR.pack(1.0f, 1.0f, 1.0f, 1.0f)),
- NEG_X(ColorABGR.pack(0.9F, 0.9F, 0.9F, 1.0f)),
- POS_X(ColorABGR.pack(0.9F, 0.9F, 0.9F, 1.0f)),
- NEG_Z(ColorABGR.pack(0.8F, 0.8F, 0.8F, 1.0f)),
- POS_Z(ColorABGR.pack(0.8F, 0.8F, 0.8F, 1.0f));
-
- public static final CloudFace[] VALUES = CloudFace.values();
- public static final int COUNT = VALUES.length;
-
- private final int color;
-
- CloudFace(int color) {
- this.color = color;
- }
-
- public int getColor() {
- return this.color;
- }
+ private static boolean isTransparent(int argb) {
+ return ColorARGB.unpackAlpha(argb) < 10;
}
- private static class CloudFaceSet {
- public static int empty() {
- return 0;
- }
-
- public static boolean contains(int set, CloudFace face) {
- return (set & (1 << face.ordinal())) != 0;
- }
-
- public static int add(int set, CloudFace face) {
- return set | (1 << face.ordinal());
- }
-
- public static int remove(int set, CloudFace face) {
- return set & ~(1 << face.ordinal());
- }
-
- public static int all() {
- return (1 << CloudFace.COUNT) - 1;
- }
+ private static int taxicabDistance(int x, int z) {
+ return Math.abs(x) + Math.abs(z);
}
- private static boolean isTransparentCell(int color) {
- return ColorARGB.unpackAlpha(color) <= 1;
+ private static FogParameters copyShaderFogParameters(FogParameters shaderFog) {
+ return new FogParameters(
+ shaderFog.start(),
+ shaderFog.end(),
+ shaderFog.shape(),
+ shaderFog.red(),
+ shaderFog.green(),
+ shaderFog.blue(),
+ shaderFog.alpha());
}
private static class CloudTextureData {
private final byte[] faces;
private final int[] colors;
- private boolean isBlank;
private final int width, height;
- public CloudTextureData(NativeImage texture) {
- int width = texture.getWidth();
- int height = texture.getHeight();
-
+ private CloudTextureData(int width, int height) {
this.faces = new byte[width * height];
this.colors = new int[width * height];
- this.isBlank = true;
this.width = width;
this.height = height;
+ }
+
+ public Slice slice(int originX, int originY, int radius) {
+ final var src = this;
+ final var dst = new CloudTextureData.Slice(radius);
+
+ for (int dstY = 0; dstY < dst.height; dstY++) {
+ int srcX = Math.floorMod(originX - radius, this.width);
+ int srcY = Math.floorMod(originY - radius + dstY, this.height);
- this.loadTextureData(texture, width, height);
+ int dstX = 0;
+
+ while (dstX < dst.width) {
+ final int length = Math.min(src.width - srcX, dst.width - dstX);
+
+ final int srcPos = getCellIndex(srcX, srcY, src.width);
+ final int dstPos = getCellIndex(dstX, dstY, dst.width);
+
+ System.arraycopy(this.faces, srcPos, dst.faces, dstPos, length);
+ System.arraycopy(this.colors, srcPos, dst.colors, dstPos, length);
+
+ srcX = 0;
+ dstX += length;
+ }
+ }
+
+ return dst;
+ }
+
+ public static @Nullable CloudTextureData load(NativeImage image) {
+ final int width = image.getWidth();
+ final int height = image.getHeight();
+
+ var data = new CloudTextureData(width, height);
+
+ if (!data.loadTextureData(image, width, height)) {
+ return null; // The texture is empty, so it isn't necessary to render it
+ }
+
+ return data;
}
- private void loadTextureData(NativeImage texture, int width, int height) {
+ private boolean loadTextureData(NativeImage texture, int width, int height) {
+ Validate.isTrue(this.width == width);
+ Validate.isTrue(this.height == height);
+
+ boolean containsData = false;
+
for (int x = 0; x < width; x++) {
for (int z = 0; z < height; z++) {
- int index = this.getCellIndex(x, z);
- int color = texture.getPixelRGBA(x, z);
+ int color = texture.getPixel(x, z);
+ if (isTransparent(color)) {
+ continue;
+ }
+
+ int index = getCellIndex(x, z, width);
this.colors[index] = color;
+ this.faces[index] = (byte) getOpenFaces(texture, color, x, z);
- if (!isTransparentCell(color)) {
- this.faces[index] = (byte) getOpenFaces(texture, color, x, z);
- this.isBlank = false;
- }
+ containsData = true;
}
}
+
+ return containsData;
}
private static int getOpenFaces(NativeImage image, int color, int x, int z) {
// Since the cloud texture is only 2D, nothing can hide the top or bottom faces
- int faces = CloudFaceSet.empty();
- faces = CloudFaceSet.add(faces, CloudFace.NEG_Y);
- faces = CloudFaceSet.add(faces, CloudFace.POS_Y);
+ int faces = FACE_MASK_NEG_Y | FACE_MASK_POS_Y;
// Generate faces where the neighbor cell is a different color
// Do not generate duplicate faces between two cells
@@ -555,7 +680,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) {
int neighbor = getNeighborTexel(image, x - 1, z);
if (color != neighbor) {
- faces = CloudFaceSet.add(faces, CloudFace.NEG_X);
+ faces |= FACE_MASK_NEG_X;
}
}
@@ -564,7 +689,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) {
int neighbor = getNeighborTexel(image, x + 1, z);
if (color != neighbor) {
- faces = CloudFaceSet.add(faces, CloudFace.POS_X);
+ faces |= FACE_MASK_POS_X;
}
}
@@ -573,7 +698,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) {
int neighbor = getNeighborTexel(image, x, z - 1);
if (color != neighbor) {
- faces = CloudFaceSet.add(faces, CloudFace.NEG_Z);
+ faces |= FACE_MASK_NEG_Z;
}
}
@@ -582,7 +707,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) {
int neighbor = getNeighborTexel(image, x, z + 1);
if (color != neighbor) {
- faces = CloudFaceSet.add(faces, CloudFace.POS_Z);
+ faces |= FACE_MASK_POS_Z;
}
}
@@ -593,7 +718,7 @@ private static int getNeighborTexel(NativeImage image, int x, int z) {
x = wrapTexelCoord(x, 0, image.getWidth() - 1);
z = wrapTexelCoord(z, 0, image.getHeight() - 1);
- return image.getPixelRGBA(x, z);
+ return image.getPixel(x, z);
}
private static int wrapTexelCoord(int coord, int min, int max) {
@@ -608,31 +733,59 @@ private static int wrapTexelCoord(int coord, int min, int max) {
return coord;
}
- public int getCellFaces(int index) {
- return this.faces[index];
+ private static int getCellIndex(int x, int z, int pitch) {
+ return (z * pitch) + x;
}
- public int getCellColor(int index) {
- return this.colors[index];
- }
+ public static class Slice {
+ private final int width, height;
+ private final int radius;
+ private final byte[] faces;
+ private final int[] colors;
- private int getCellIndexWrapping(int x, int z) {
- return this.getCellIndex(
- Math.floorMod(x, this.width),
- Math.floorMod(z, this.height)
- );
- }
+ public Slice(int radius) {
+ this.width = 1 + (radius * 2);
+ this.height = 1 + (radius * 2);
+ this.radius = radius;
+ this.faces = new byte[this.width * this.height];
+ this.colors = new int[this.width * this.height];
+ }
+
+ public int getCellIndex(int x, int z) {
+ return CloudTextureData.getCellIndex(x + this.radius, z + this.radius, this.width);
+ }
- private int getCellIndex(int x, int z) {
- return (x * this.width) + z;
+ public int getCellFaces(int index) {
+ return Byte.toUnsignedInt(this.faces[index]);
+ }
+
+ public int getCellColor(int index) {
+ return this.colors[index];
+ }
}
}
- public record CloudGeometry(VertexBuffer vertexBuffer, CloudGeometryParameters params) {
+ public record CloudGeometry(@Nullable VertexBuffer vertexBuffer, CloudGeometryParameters params) {
}
- public record CloudGeometryParameters(int originX, int originZ, int radius, int orientation, CloudStatus renderMode) {
+ public record CloudGeometryParameters(int originX, int originZ, int radius, @Nullable ViewOrientation orientation, CloudStatus renderMode) {
}
+
+ private enum ViewOrientation {
+ BELOW_CLOUDS, // Top faces should *not* be rendered
+ INSIDE_CLOUDS, // All faces *must* be rendered
+ ABOVE_CLOUDS; // Bottom faces should *not* be rendered
+
+ public static @NotNull ViewOrientation getOrientation(Vec3 camera, float minY, float maxY) {
+ if (camera.y() <= minY + 0.125f /* epsilon */) {
+ return ViewOrientation.BELOW_CLOUDS;
+ } else if (camera.y() >= maxY - 0.125f /* epsilon */) {
+ return ViewOrientation.ABOVE_CLOUDS;
+ } else {
+ return ViewOrientation.INSIDE_CLOUDS;
+ }
+ }
+ }
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java
index 1de8965c12..854141ec5f 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java
@@ -1,6 +1,7 @@
package net.caffeinemc.mods.sodium.client.render.immediate.model;
import com.mojang.blaze3d.vertex.PoseStack;
+import net.caffeinemc.mods.sodium.api.util.ColorMixer;
import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView;
import net.caffeinemc.mods.sodium.api.math.MatrixHelper;
import net.caffeinemc.mods.sodium.api.util.ColorABGR;
@@ -23,7 +24,7 @@ private static int mergeLighting(int stored, int calculated) {
private static final boolean MULTIPLY_ALPHA = PlatformRuntimeInformation.getInstance().usesAlphaMultiplication();
- public static void writeQuadVertices(VertexBufferWriter writer, PoseStack.Pose matrices, ModelQuadView quad, int color, int light, int overlay) {
+ public static void writeQuadVertices(VertexBufferWriter writer, PoseStack.Pose matrices, ModelQuadView quad, int color, int light, int overlay, boolean colorize) {
Matrix3f matNormal = matrices.normal();
Matrix4f matPosition = matrices.pose();
@@ -37,7 +38,13 @@ public static void writeQuadVertices(VertexBufferWriter writer, PoseStack.Pose m
float y = quad.getY(i);
float z = quad.getZ(i);
- int newLight = mergeLighting(quad.getLight(i), light);
+ int newLight = mergeLighting(quad.getMaxLightQuad(i), light);
+
+ int newColor = color;
+
+ if (colorize) {
+ newColor = ColorMixer.mulComponentWise(newColor, quad.getColor(i));
+ }
// The packed transformed normal vector
int normal = MatrixHelper.transformNormal(matNormal, matrices.trustedNormals, quad.getAccurateNormal(i));
@@ -47,7 +54,7 @@ public static void writeQuadVertices(VertexBufferWriter writer, PoseStack.Pose m
float yt = MatrixHelper.transformPositionY(matPosition, x, y, z);
float zt = MatrixHelper.transformPositionZ(matPosition, x, y, z);
- EntityVertex.write(ptr, xt, yt, zt, color, quad.getTexU(i), quad.getTexV(i), overlay, newLight, normal);
+ EntityVertex.write(ptr, xt, yt, zt, newColor, quad.getTexU(i), quad.getTexV(i), overlay, newLight, normal);
ptr += EntityVertex.STRIDE;
}
@@ -116,4 +123,8 @@ public static void writeQuadVertices(VertexBufferWriter writer, PoseStack.Pose m
writer.push(stack, buffer, 4, EntityVertex.FORMAT);
}
}
+
+ public static boolean shouldMultiplyAlpha() {
+ return MULTIPLY_ALPHA;
+ }
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java
index 36ee747940..fd7743dac3 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java
@@ -10,22 +10,14 @@
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.lwjgl.system.MemoryStack;
-import org.lwjgl.system.MemoryUtil;
-public class EntityRenderer {
+import static net.caffeinemc.mods.sodium.client.render.immediate.model.ModelCuboid.*;
+public class EntityRenderer {
private static final int NUM_CUBE_VERTICES = 8;
private static final int NUM_CUBE_FACES = 6;
private static final int NUM_FACE_VERTICES = 4;
- private static final int
- FACE_NEG_Y = 0, // DOWN
- FACE_POS_Y = 1, // UP
- FACE_NEG_Z = 2, // NORTH
- FACE_POS_Z = 3, // SOUTH
- FACE_NEG_X = 4, // WEST
- FACE_POS_X = 5; // EAST
-
private static final int
VERTEX_X1_Y1_Z1 = 0,
VERTEX_X2_Y1_Z1 = 1,
@@ -38,17 +30,10 @@ public class EntityRenderer {
private static final Matrix3f lastMatrix = new Matrix3f();
- private static final long SCRATCH_BUFFER = MemoryUtil.nmemAlignedAlloc(64, NUM_CUBE_FACES * NUM_FACE_VERTICES * EntityVertex.STRIDE);
+ private static final int VERTEX_BUFFER_BYTES = NUM_CUBE_FACES * NUM_FACE_VERTICES * EntityVertex.STRIDE;
private static final Vector3f[] CUBE_CORNERS = new Vector3f[NUM_CUBE_VERTICES];
- private static final int[][] CUBE_VERTICES = new int[][] {
- { VERTEX_X2_Y1_Z2, VERTEX_X1_Y1_Z2, VERTEX_X1_Y1_Z1, VERTEX_X2_Y1_Z1 },
- { VERTEX_X2_Y2_Z1, VERTEX_X1_Y2_Z1, VERTEX_X1_Y2_Z2, VERTEX_X2_Y2_Z2 },
- { VERTEX_X2_Y1_Z1, VERTEX_X1_Y1_Z1, VERTEX_X1_Y2_Z1, VERTEX_X2_Y2_Z1 },
- { VERTEX_X1_Y1_Z2, VERTEX_X2_Y1_Z2, VERTEX_X2_Y2_Z2, VERTEX_X1_Y2_Z2 },
- { VERTEX_X2_Y1_Z2, VERTEX_X2_Y1_Z1, VERTEX_X2_Y2_Z1, VERTEX_X2_Y2_Z2 },
- { VERTEX_X1_Y1_Z1, VERTEX_X1_Y1_Z2, VERTEX_X1_Y2_Z2, VERTEX_X1_Y2_Z1 },
- };
+ private static final int[][] CUBE_VERTICES = new int[NUM_CUBE_FACES][];
private static final Vector3f[][] VERTEX_POSITIONS = new Vector3f[NUM_CUBE_FACES][NUM_FACE_VERTICES];
private static final Vector3f[][] VERTEX_POSITIONS_MIRRORED = new Vector3f[NUM_CUBE_FACES][NUM_FACE_VERTICES];
@@ -60,6 +45,13 @@ public class EntityRenderer {
private static final int[] CUBE_NORMALS_MIRRORED = new int[NUM_CUBE_FACES];
static {
+ CUBE_VERTICES[FACE_NEG_Y] = new int[] { VERTEX_X2_Y1_Z2, VERTEX_X1_Y1_Z2, VERTEX_X1_Y1_Z1, VERTEX_X2_Y1_Z1 };
+ CUBE_VERTICES[FACE_POS_Y] = new int[] { VERTEX_X2_Y2_Z1, VERTEX_X1_Y2_Z1, VERTEX_X1_Y2_Z2, VERTEX_X2_Y2_Z2 };
+ CUBE_VERTICES[FACE_NEG_Z] = new int[] { VERTEX_X2_Y1_Z1, VERTEX_X1_Y1_Z1, VERTEX_X1_Y2_Z1, VERTEX_X2_Y2_Z1 };
+ CUBE_VERTICES[FACE_POS_Z] = new int[] { VERTEX_X1_Y1_Z2, VERTEX_X2_Y1_Z2, VERTEX_X2_Y2_Z2, VERTEX_X1_Y2_Z2 };
+ CUBE_VERTICES[FACE_NEG_X] = new int[] { VERTEX_X2_Y1_Z2, VERTEX_X2_Y1_Z1, VERTEX_X2_Y2_Z1, VERTEX_X2_Y2_Z2 };
+ CUBE_VERTICES[FACE_POS_X] = new int[] { VERTEX_X1_Y1_Z1, VERTEX_X1_Y1_Z2, VERTEX_X1_Y2_Z2, VERTEX_X1_Y2_Z1 };
+
for (int cornerIndex = 0; cornerIndex < NUM_CUBE_VERTICES; cornerIndex++) {
CUBE_CORNERS[cornerIndex] = new Vector3f();
}
@@ -81,24 +73,26 @@ public class EntityRenderer {
public static void renderCuboid(PoseStack.Pose matrices, VertexBufferWriter writer, ModelCuboid cuboid, int light, int overlay, int color) {
prepareNormalsIfChanged(matrices);
-
prepareVertices(matrices, cuboid);
- var vertexCount = emitQuads(cuboid, color, overlay, light);
-
try (MemoryStack stack = MemoryStack.stackPush()) {
- writer.push(stack, SCRATCH_BUFFER, vertexCount, EntityVertex.FORMAT);
+ final var vertexBuffer = stack.nmalloc(16, VERTEX_BUFFER_BYTES);
+ final var vertexCount = emitQuads(vertexBuffer, cuboid, color, overlay, light);
+
+ if (vertexCount > 0) {
+ writer.push(stack, vertexBuffer, vertexCount, EntityVertex.FORMAT);
+ }
}
}
- private static int emitQuads(ModelCuboid cuboid, int color, int overlay, int light) {
+ private static int emitQuads(final long buffer, ModelCuboid cuboid, int color, int overlay, int light) {
final var positions = cuboid.mirror ? VERTEX_POSITIONS_MIRRORED : VERTEX_POSITIONS;
final var textures = cuboid.mirror ? VERTEX_TEXTURES_MIRRORED : VERTEX_TEXTURES;
final var normals = cuboid.mirror ? CUBE_NORMALS_MIRRORED : CUBE_NORMALS;
var vertexCount = 0;
- long ptr = SCRATCH_BUFFER;
+ long ptr = buffer;
for (int quadIndex = 0; quadIndex < NUM_CUBE_FACES; quadIndex++) {
if (!cuboid.shouldDrawFace(quadIndex)) {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java
index 57a3dc83f0..4a13b857ca 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java
@@ -2,15 +2,26 @@
import java.util.Set;
import net.minecraft.core.Direction;
+import org.jetbrains.annotations.NotNull;
public class ModelCuboid {
+ // The ordering needs to be the same as Minecraft, otherwise some core shader replacements
+ // will be unable to identify the facing.
+ public static final int
+ FACE_NEG_Y = 0, // DOWN
+ FACE_POS_Y = 1, // UP
+ FACE_NEG_X = 2, // WEST
+ FACE_NEG_Z = 3, // NORTH
+ FACE_POS_X = 4, // EAST
+ FACE_POS_Z = 5; // SOUTH
+
public final float x1, y1, z1;
public final float x2, y2, z2;
public final float u0, u1, u2, u3, u4, u5;
public final float v0, v1, v2;
- private final int faces;
+ private final int cullBitmask;
public final boolean mirror;
@@ -63,16 +74,27 @@ public ModelCuboid(int u, int v,
this.mirror = mirror;
- int faces = 0;
+ int cullBitmask = 0;
- for (var dir : renderDirections) {
- faces |= 1 << dir.ordinal();
+ for (var direction : renderDirections) {
+ cullBitmask |= 1 << getFaceIndex(direction);
}
- this.faces = faces;
+ this.cullBitmask = cullBitmask;
+ }
+
+ public boolean shouldDrawFace(int faceIndex) {
+ return (this.cullBitmask & (1 << faceIndex)) != 0;
}
- public boolean shouldDrawFace(int quadIndex) {
- return (this.faces & (1 << quadIndex)) != 0;
+ public static int getFaceIndex(@NotNull Direction dir) {
+ return switch (dir) {
+ case DOWN -> FACE_NEG_Y;
+ case UP -> FACE_POS_Y;
+ case NORTH -> FACE_NEG_Z;
+ case SOUTH -> FACE_POS_Z;
+ case WEST -> FACE_NEG_X;
+ case EAST -> FACE_POS_X;
+ };
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java
index 7679b20dc3..a124dcabb7 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java
@@ -85,4 +85,13 @@ static PlatformBlockAccess getInstance() {
* @return Whether this block entity should activate the outline shader.
*/
boolean shouldBlockEntityGlow(BlockEntity blockEntity, LocalPlayer player);
+
+ /**
+ * Determines if a fluid adjacent to the block on the given side should not be rendered.
+ *
+ * @param adjDirection the face of this block that the fluid is adjacent to
+ * @param fluid the fluid that is touching that face
+ * @return if this block should cause the fluid's face to not render
+ */
+ boolean shouldOccludeFluid(Direction adjDirection, BlockState adjBlockState, FluidState fluid);
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/UInt32.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/UInt32.java
new file mode 100644
index 0000000000..9e04adbac1
--- /dev/null
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/UInt32.java
@@ -0,0 +1,22 @@
+package net.caffeinemc.mods.sodium.client.util;
+
+public class UInt32 {
+ public static long upcast(int x) {
+ return Integer.toUnsignedLong(x);
+ }
+
+ public static int downcast(long x) {
+ if (x < 0) {
+ throw new IllegalArgumentException("x < 0");
+ } else if (x >= (1L << 32)) {
+ throw new IllegalArgumentException("x >= (1 << 32)");
+ }
+
+ return (int) x;
+ }
+
+ // Note: This is unsafe when (x) exceeds the maximum range of a UInt32.
+ public static int uncheckedDowncast(long x) {
+ return (int) x;
+ }
+}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/WeightedRandomListExtension.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/WeightedRandomListExtension.java
new file mode 100644
index 0000000000..3476471c8b
--- /dev/null
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/WeightedRandomListExtension.java
@@ -0,0 +1,7 @@
+package net.caffeinemc.mods.sodium.client.util;
+
+import net.minecraft.util.RandomSource;
+
+public interface WeightedRandomListExtension {
+ T sodium$getQuick(RandomSource random);
+}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java
index 63a47a933a..2fb9d6252a 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java
@@ -3,7 +3,7 @@
import java.util.Arrays;
/**
- * Originally authored here: https://github.com/CaffeineMC/sodium-fabric/blob/ddfb9f21a54bfb30aa876678204371e94d8001db/src/main/java/net/caffeinemc/sodium/util/collections/BitArray.java
+ * Originally authored here: https://github.com/CaffeineMC/sodium/blob/ddfb9f21a54bfb30aa876678204371e94d8001db/src/main/java/net/caffeinemc/sodium/util/collections/BitArray.java
* @author burgerindividual
*/
public class BitArray {
@@ -18,7 +18,7 @@ public class BitArray {
/**
* Returns {@param num} aligned to the next multiple of {@param alignment}.
*
- * Taken from https://github.com/CaffeineMC/sodium-fabric/blob/1.19.x/next/components/gfx-utils/src/main/java/net/caffeinemc/gfx/util/misc/MathUtil.java
+ * Taken from https://github.com/CaffeineMC/sodium/blob/1.19.x/next/components/gfx-utils/src/main/java/net/caffeinemc/gfx/util/misc/MathUtil.java
*
* @param num The number that will be rounded if needed
* @param alignment The multiple that the output will be rounded to (must be a
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java
index f3b2d70640..e0ac2586fe 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java
@@ -4,67 +4,63 @@
import net.minecraft.util.Mth;
public class BoxBlur {
-
- public static void blur(ColorBuffer buf, ColorBuffer tmp, int radius) {
- if (buf.width != tmp.width || buf.height != tmp.height) {
- throw new IllegalArgumentException("Color buffers must have same dimensions");
- }
-
- if (isHomogenous(buf.data)) {
+ public static void blur(int[] src, int[] tmp, int width, int height, int radius) {
+ if (isHomogenous(src)) {
return;
}
- blurImpl(buf.data, tmp.data, buf.width, buf.height, radius); // X-axis
- blurImpl(tmp.data, buf.data, buf.width, buf.height, radius); // Y-axis
+ blurImpl(src, tmp, radius, width - radius, width, 0, height, height, radius); // X-axis
+ blurImpl(tmp, src, radius, width - radius, width, radius, height - radius, height, radius); // Y-axis
}
- private static void blurImpl(int[] src, int[] dst, int width, int height, int radius) {
- int multiplier = getAveragingMultiplier((radius * 2) + 1);
-
- for (int y = 0; y < height; y++) {
- int srcRowOffset = ColorBuffer.getIndex(0, y, width);
-
- int red, green, blue;
-
- {
- int color = src[srcRowOffset];
- red = ColorARGB.unpackRed(color);
- green = ColorARGB.unpackGreen(color);
- blue = ColorARGB.unpackBlue(color);
+ private static void blurImpl(int[] src, int[] dst, int x0, int x1, int width, int y0, int y1, int height, int radius) {
+ int windowSize = (radius * 2) + 1;
+ int multiplier = getAveragingMultiplier(windowSize);
+
+ for (int y = y0; y < y1; y++) {
+ int accR = 0;
+ int accG = 0;
+ int accB = 0;
+
+ int windowPivotIndex = ColorBuffer.getIndex(x0, y, width);
+ int windowTailIndex = windowPivotIndex - radius;
+ int windowHeadIndex = windowPivotIndex + radius;
+
+ // Initialize window
+ for (int x = -radius; x <= radius; x++) {
+ var color = src[windowPivotIndex + x];
+ accR += ColorARGB.unpackRed(color);
+ accG += ColorARGB.unpackGreen(color);
+ accB += ColorARGB.unpackBlue(color);
}
- // Extend the window backwards by repeating the colors at the edge N times
- red += red * radius;
- green += green * radius;
- blue += blue * radius;
-
- // Extend the window forwards by sampling ahead N times
- for (int x = 1; x <= radius; x++) {
- var color = src[srcRowOffset + x];
- red += ColorARGB.unpackRed(color);
- green += ColorARGB.unpackGreen(color);
- blue += ColorARGB.unpackBlue(color);
- }
+ // Scan forwards
+ int x = x0;
- for (int x = 0; x < width; x++) {
+ while (true) {
// The x and y coordinates are transposed to flip the output image
- dst[ColorBuffer.getIndex(y, x, width)] = averageRGB(red, green, blue, multiplier);
+ // noinspection SuspiciousNameCombination
+ dst[ColorBuffer.getIndex(y, x, width)] = averageRGB(accR, accG, accB, multiplier);
+ x++;
+
+ if (x >= x1) {
+ break;
+ }
{
// Remove the color values that are behind the window
- var color = src[srcRowOffset + Math.max(0, x - radius)];
-
- red -= ColorARGB.unpackRed(color);
- green -= ColorARGB.unpackGreen(color);
- blue -= ColorARGB.unpackBlue(color);
+ var color = src[windowTailIndex++];
+ accR -= ColorARGB.unpackRed(color);
+ accG -= ColorARGB.unpackGreen(color);
+ accB -= ColorARGB.unpackBlue(color);
}
{
// Add the color values that are ahead of the window
- var color = src[srcRowOffset + Math.min(width - 1, x + radius + 1)];
- red += ColorARGB.unpackRed(color);
- green += ColorARGB.unpackGreen(color);
- blue += ColorARGB.unpackBlue(color);
+ var color = src[++windowHeadIndex];
+ accR += ColorARGB.unpackRed(color);
+ accG += ColorARGB.unpackGreen(color);
+ accB += ColorARGB.unpackBlue(color);
}
}
}
@@ -108,7 +104,7 @@ private static boolean isHomogenous(int[] array) {
}
public static class ColorBuffer {
- protected final int[] data;
+ public final int[] data;
protected final int width, height;
public ColorBuffer(int width, int height) {
@@ -121,13 +117,12 @@ public void set(int x, int y, int color) {
this.data[getIndex(x, y, this.width)] = color;
}
-
public int get(int x, int y) {
return this.data[getIndex(x, y, this.width)];
}
public static int getIndex(int x, int y, int width) {
- return (y * width) + x;
+ return x + (y * width);
}
}
}
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/ColorSRGB.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/ColorSRGB.java
index 1c5a1aef67..72d08484ad 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/ColorSRGB.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/ColorSRGB.java
@@ -1,7 +1,8 @@
package net.caffeinemc.mods.sodium.client.util.color;
-import net.minecraft.util.FastColor;
+
+import net.caffeinemc.mods.sodium.api.util.ColorABGR;
/**
* This is a port of the fast-srgb8 library from thomcc on GitHub.
@@ -82,7 +83,7 @@ public static float srgbToLinear(int c) {
* @param a The alpha-component in linear RGB space (0 to 255)
*/
public static int linearToSrgb(float r, float g, float b, int a) {
- return FastColor.ABGR32.color(a, linearToSrgb(b), linearToSrgb(g), linearToSrgb(r));
+ return ColorABGR.pack(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b), a);
}
/**
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java
index c6b18eac52..4e261460d5 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java
@@ -359,8 +359,8 @@ public int getHeight() {
}
@Override
- public int getMinBuildHeight() {
- return this.level.getMinBuildHeight();
+ public int getMinY() {
+ return this.level.getMinY();
}
@Override
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/BiomeColorMaps.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/BiomeColorMaps.java
index a06dab7bd1..87ea7b5599 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/BiomeColorMaps.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/BiomeColorMaps.java
@@ -19,7 +19,7 @@ public static int getGrassColor(int index) {
public static int getFoliageColor(int index) {
if (index == INVALID_INDEX || index >= FoliageColor.pixels.length) {
- return FoliageColor.getDefaultColor();
+ return FoliageColor.FOLIAGE_DEFAULT;
}
return FoliageColor.pixels[index];
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelBiomeSlice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelBiomeSlice.java
index 43534b8a5d..5422902d72 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelBiomeSlice.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelBiomeSlice.java
@@ -41,8 +41,8 @@ public void update(ClientLevel level, ChunkRenderContext context) {
private void copyBiomeData(Level level, ChunkRenderContext context) {
var defaultValue = level.registryAccess()
- .registryOrThrow(Registries.BIOME)
- .getHolderOrThrow(Biomes.PLAINS);
+ .lookupOrThrow(Registries.BIOME)
+ .getOrThrow(Biomes.PLAINS);
for (int sectionX = 0; sectionX < 3; sectionX++) {
for (int sectionY = 0; sectionY < 3; sectionY++) {
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java
index 80561d0fc3..370635f67e 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java
@@ -4,7 +4,6 @@
import net.caffeinemc.mods.sodium.client.util.color.BoxBlur;
import net.caffeinemc.mods.sodium.client.util.color.BoxBlur.ColorBuffer;
import net.caffeinemc.mods.sodium.client.world.cloned.ChunkRenderContext;
-import net.minecraft.client.renderer.BiomeColors;
import net.minecraft.util.Mth;
import net.minecraft.world.level.ColorResolver;
import net.minecraft.world.level.biome.Biome;
@@ -39,35 +38,36 @@ public LevelColorCache(LevelBiomeSlice biomeData, int blendRadius) {
}
public void update(ChunkRenderContext context) {
- this.minBlockX = (context.getOrigin().minBlockX() - NEIGHBOR_BLOCK_RADIUS) - this.blendRadius;
+ this.minBlockX = (context.getOrigin().minBlockX() - NEIGHBOR_BLOCK_RADIUS);
this.minBlockY = (context.getOrigin().minBlockY() - NEIGHBOR_BLOCK_RADIUS);
- this.minBlockZ = (context.getOrigin().minBlockZ() - NEIGHBOR_BLOCK_RADIUS) - this.blendRadius;
+ this.minBlockZ = (context.getOrigin().minBlockZ() - NEIGHBOR_BLOCK_RADIUS);
- this.maxBlockX = (context.getOrigin().maxBlockX() + NEIGHBOR_BLOCK_RADIUS) + this.blendRadius;
+ this.maxBlockX = (context.getOrigin().maxBlockX() + NEIGHBOR_BLOCK_RADIUS);
this.maxBlockY = (context.getOrigin().maxBlockY() + NEIGHBOR_BLOCK_RADIUS);
- this.maxBlockZ = (context.getOrigin().maxBlockZ() + NEIGHBOR_BLOCK_RADIUS) + this.blendRadius;
+ this.maxBlockZ = (context.getOrigin().maxBlockZ() + NEIGHBOR_BLOCK_RADIUS);
this.populateStamp++;
}
public int getColor(ColorResolver resolver, int blockX, int blockY, int blockZ) {
- var relBlockX = Mth.clamp(blockX, this.minBlockX, this.maxBlockX) - this.minBlockX;
- var relBlockY = Mth.clamp(blockY, this.minBlockY, this.maxBlockY) - this.minBlockY;
- var relBlockZ = Mth.clamp(blockZ, this.minBlockZ, this.maxBlockZ) - this.minBlockZ;
+ // Clamp inputs
+ blockX = Mth.clamp(blockX, this.minBlockX, this.maxBlockX) - this.minBlockX;
+ blockY = Mth.clamp(blockY, this.minBlockY, this.maxBlockY) - this.minBlockY;
+ blockZ = Mth.clamp(blockZ, this.minBlockZ, this.maxBlockZ) - this.minBlockZ;
if (!this.slices.containsKey(resolver)) {
this.initializeSlices(resolver);
}
- var slice = this.slices.get(resolver)[relBlockY];
+ var slice = this.slices.get(resolver)[blockY];
if (slice.lastPopulateStamp < this.populateStamp) {
- this.updateColorBuffers(relBlockY, resolver, slice);
+ this.updateColorBuffers(blockY, resolver, slice);
}
var buffer = slice.getBuffer();
- return buffer.get(relBlockX, relBlockZ);
+ return buffer.get(blockX + this.blendRadius, blockZ + this.blendRadius);
}
private void initializeSlices(ColorResolver resolver) {
@@ -83,19 +83,27 @@ private void initializeSlices(ColorResolver resolver) {
private void updateColorBuffers(int relY, ColorResolver resolver, Slice slice) {
int blockY = this.minBlockY + relY;
- for (int blockZ = this.minBlockZ; blockZ <= this.maxBlockZ; blockZ++) {
- for (int blockX = this.minBlockX; blockX <= this.maxBlockX; blockX++) {
+ int minBlockZ = this.minBlockZ - this.blendRadius;
+ int minBlockX = this.minBlockX - this.blendRadius;
+
+ int maxBlockZ = this.maxBlockZ + this.blendRadius;
+ int maxBlockX = this.maxBlockX + this.blendRadius;
+
+ ColorBuffer buffer = slice.buffer;
+
+ for (int blockZ = minBlockZ; blockZ <= maxBlockZ; blockZ++) {
+ for (int blockX = minBlockX; blockX <= maxBlockX; blockX++) {
Biome biome = this.biomeData.getBiome(blockX, blockY, blockZ).value();
- int relBlockX = blockX - this.minBlockX;
- int relBlockZ = blockZ - this.minBlockZ;
+ int relBlockX = blockX - minBlockX;
+ int relBlockZ = blockZ - minBlockZ;
- slice.buffer.set(relBlockX, relBlockZ, resolver.getColor(biome, blockX, blockZ));
+ buffer.set(relBlockX, relBlockZ, resolver.getColor(biome, blockX, blockZ));
}
}
if (this.blendRadius > 0) {
- BoxBlur.blur(slice.buffer, this.tempColorBuffer, this.blendRadius);
+ BoxBlur.blur(buffer.data, this.tempColorBuffer.data, this.sizeXZ, this.sizeXZ, this.blendRadius);
}
slice.lastPopulateStamp = this.populateStamp;
diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java
index 4ef3a083ad..6390b26921 100644
--- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java
+++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java
@@ -185,7 +185,7 @@ private static Int2ReferenceMap
*
*
When updating Sodium to new releases of the game, please check for new
- * ways the fog can be reduced in {@link FogRenderer#setupFog(Camera, FogRenderer.FogMode, float, boolean, float)} ()}.
+ * ways the fog can be reduced in {@link FogRenderer#setupFog(Camera, FogRenderer.FogMode, org.joml.Vector4f, float, boolean, float)} ()}.