-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Metadata picking preparation #12075
base: main
Are you sure you want to change the base?
Metadata picking preparation #12075
Changes from 20 commits
656c638
247102f
e561dec
cc1b826
a28f62d
122c4e8
a44ac07
e0c57f6
dc7552c
609e277
927d01b
461821b
261dbba
3817389
a183fd4
7848f5d
66b3252
016a1a8
d575317
4d0b722
2f29019
a1989c0
88500ec
fe4c2bd
f8bac8f
8a24da2
ebddfe1
c0eaf7b
e732550
775113d
2056da0
0e6b7f6
45727e8
2a948cf
ddd5fb8
4347a23
d813dba
0ad2bc5
aa75922
9c3c081
c854235
384b2fa
3c1b497
a005521
30e80b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import defined from "../Core/defined.js"; | |
import DrawCommand from "../Renderer/DrawCommand.js"; | ||
import RenderState from "../Renderer/RenderState.js"; | ||
import ShaderSource from "../Renderer/ShaderSource.js"; | ||
import MetadataType from "./MetadataType.js"; | ||
|
||
/** | ||
* @private | ||
|
@@ -375,6 +376,179 @@ DerivedCommand.createPickDerivedCommand = function ( | |
return result; | ||
}; | ||
|
||
/** | ||
* @private | ||
*/ | ||
function replaceDefine(defines, defineName, newDefineValue) { | ||
const n = defines.length; | ||
for (let i = 0; i < n; i++) { | ||
if (defines[i].startsWith(defineName)) { | ||
defines[i] = `${defineName} ${newDefineValue}`; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @private | ||
*/ | ||
function getComponentCount(classProperty) { | ||
if (!classProperty.isArray) { | ||
return MetadataType.getComponentCount(classProperty.type); | ||
} | ||
return classProperty.arrayLength; | ||
} | ||
function getGlslType(classProperty) { | ||
const componentCount = getComponentCount(classProperty); | ||
if (classProperty.normalized) { | ||
if (componentCount === 1) { | ||
return "float"; | ||
} | ||
return `vec${componentCount}`; | ||
} | ||
if (componentCount === 1) { | ||
return "int"; | ||
} | ||
return `ivec${componentCount}`; | ||
} | ||
|
||
/** | ||
* @private | ||
*/ | ||
function getPickMetadataShaderProgram( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be easier to read with some types in the doc. I'm assuming There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would a Seriously: I'm a HUGE fan of the "Boy Scout Rule" of programming:
Derived from the rule "Leave the campground cleaner that you found it". Now one could argue in how far this applies when boy scouts visit something that isn't a "campground" to begin with. However, I added the information that the |
||
context, | ||
shaderProgram, | ||
pickedMetadataInfo | ||
) { | ||
const schemaId = pickedMetadataInfo.schemaId; | ||
const className = pickedMetadataInfo.className; | ||
const propertyName = pickedMetadataInfo.propertyName; | ||
const keyword = `pickMetadata-${schemaId}-${className}-${propertyName}`; | ||
let shader = context.shaderCache.getDerivedShaderProgram( | ||
shaderProgram, | ||
keyword | ||
); | ||
if (defined(shader)) { | ||
return shader; | ||
} | ||
|
||
const classProperty = pickedMetadataInfo.classProperty; | ||
const glslType = getGlslType(classProperty); | ||
|
||
// Define the components that will go into the output `metadataValues`. | ||
// By default, all of them are 0.0. | ||
const sourceValueStrings = ["0.0", "0.0", "0.0", "0.0"]; | ||
const componentCount = getComponentCount(classProperty); | ||
if (componentCount === 1) { | ||
// When the property is a scalar, store its value directly | ||
// in `metadataValues.x` | ||
sourceValueStrings[0] = `float(value)`; | ||
} else { | ||
// When the property is an array, store the array elements | ||
// in `metadataValues.x/y/z/w` | ||
const components = ["x", "y", "z", "w"]; | ||
for (let i = 0; i < componentCount; i++) { | ||
const component = components[i]; | ||
const valueString = `value.${component}`; | ||
sourceValueStrings[i] = `float(${valueString})`; | ||
} | ||
} | ||
|
||
// Make sure that the `metadataValues` components are all in | ||
// the range [0, 1] (which will result in RGBA components | ||
// in [0, 255] during rendering) | ||
if (!classProperty.normalized) { | ||
for (let i = 0; i < componentCount; i++) { | ||
sourceValueStrings[i] += " / 255.0"; | ||
} | ||
} | ||
|
||
let fs = shaderProgram.fragmentShaderSource; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know this is common in existing code, but a two-letter variable name is not really our current recommendation. It's "short" but not "descriptive" 😃 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed the code to use |
||
const newDefines = [...fs.defines]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. (After additionally verifying that |
||
newDefines.push("METADATA_PICKING_ENABLED"); | ||
|
||
// Replace the defines of the shader, using the type, property | ||
// access, and value components that have been determined | ||
replaceDefine(newDefines, "METADATA_PICKING_VALUE_TYPE", glslType); | ||
replaceDefine( | ||
newDefines, | ||
"METADATA_PICKING_VALUE_STRING", | ||
`metadata.${propertyName}` | ||
); | ||
replaceDefine( | ||
newDefines, | ||
"METADATA_PICKING_VALUE_COMPONENT_X", | ||
sourceValueStrings[0] | ||
); | ||
replaceDefine( | ||
newDefines, | ||
"METADATA_PICKING_VALUE_COMPONENT_Y", | ||
sourceValueStrings[1] | ||
); | ||
replaceDefine( | ||
newDefines, | ||
"METADATA_PICKING_VALUE_COMPONENT_Z", | ||
sourceValueStrings[2] | ||
); | ||
replaceDefine( | ||
newDefines, | ||
"METADATA_PICKING_VALUE_COMPONENT_W", | ||
sourceValueStrings[3] | ||
); | ||
|
||
// XXX_METADATA_PICKING | ||
//*/ | ||
console.log("newDefines"); | ||
for (const d of newDefines) { | ||
console.log(d); | ||
} | ||
//*/ | ||
fs = new ShaderSource({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So far, all we used from the old There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, it's |
||
sources: fs.sources, | ||
defines: newDefines, | ||
}); | ||
shader = context.shaderCache.createDerivedShaderProgram( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here again, the previous use of this variable could be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, called it |
||
shaderProgram, | ||
keyword, | ||
{ | ||
vertexShaderSource: shaderProgram.vertexShaderSource, | ||
fragmentShaderSource: fs, | ||
attributeLocations: shaderProgram._attributeLocations, | ||
} | ||
); | ||
return shader; | ||
} | ||
|
||
/** | ||
* @private | ||
*/ | ||
DerivedCommand.createPickMetadataDerivedCommand = function ( | ||
scene, | ||
command, | ||
context, | ||
result | ||
) { | ||
if (!defined(result)) { | ||
result = {}; | ||
} | ||
result.pickMetadataCommand = DrawCommand.shallowClone( | ||
command, | ||
result.pickMetadataCommand | ||
); | ||
|
||
result.pickMetadataCommand.shaderProgram = getPickMetadataShaderProgram( | ||
context, | ||
command.shaderProgram, | ||
command.pickedMetadataInfo | ||
); | ||
result.pickMetadataCommand.renderState = getPickRenderState( | ||
scene, | ||
command.renderState | ||
); | ||
result.shaderProgramId = command.shaderProgram.id; | ||
|
||
return result; | ||
}; | ||
|
||
function getHdrShaderProgram(context, shaderProgram) { | ||
let shader = context.shaderCache.getDerivedShaderProgram( | ||
shaderProgram, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import MetadataComponentType from "./MetadataComponentType.js"; | ||
|
||
/** | ||
* Utility functions for metadata picking. | ||
* | ||
* These are used by the `Picking.pickMetadata` function to decode | ||
* the metadata values that have been read from the frame buffer | ||
* into the actual metadata values, according to the structure | ||
* defined by the `MetadataClassProperty`. | ||
* | ||
* This is marked as 'private', but supposed to be used in Picking.js. | ||
* | ||
* @private | ||
*/ | ||
function MetadataPicking() {} | ||
|
||
/** | ||
* Returns the value at the specified inded of the given data view, | ||
* interpreting the data to have the given component type. | ||
* | ||
* @param {MetadataComponentType} componentType The `MetadataComponentType` | ||
* @param {DataView} dataView The data view | ||
* @param {number} index The index | ||
* @returns {number|bigint|undefined} The value | ||
* | ||
* @private | ||
*/ | ||
MetadataPicking.decodeMetadataValue = function ( | ||
componentType, | ||
dataView, | ||
index | ||
) { | ||
switch (componentType) { | ||
case MetadataComponentType.INT8: | ||
return dataView.getInt8(index); | ||
case MetadataComponentType.UINT8: | ||
return dataView.getUint8(index); | ||
case MetadataComponentType.INT16: | ||
return dataView.getInt16(index); | ||
case MetadataComponentType.UINT16: | ||
return dataView.getUint16(index); | ||
case MetadataComponentType.INT32: | ||
return dataView.getInt32(index); | ||
case MetadataComponentType.UINT32: | ||
return dataView.getUint32(index); | ||
case MetadataComponentType.INT64: | ||
return dataView.getBigInt64(index); | ||
case MetadataComponentType.UINT64: | ||
return dataView.getBigUint64(index); | ||
case MetadataComponentType.FLOAT32: | ||
return dataView.getFloat32(index); | ||
case MetadataComponentType.FLOAT64: | ||
return dataView.getFloat64(index); | ||
} | ||
// Appropriate error handling? | ||
return undefined; | ||
}; | ||
|
||
/** | ||
* Decode the given raw values into a metadata property value. | ||
* | ||
* The given values are a `Uint8Array` containing the RGBA | ||
* values that have been read from the metadata picking | ||
* frame buffer. They are assumed to contain the value for | ||
* the given class property, as encoded by the | ||
* `ModelDrawCommands` for metadata picking. | ||
* | ||
* @param {MetadataClassProperty} classProperty The `MetadataClassProperty` | ||
* @param {Uint8Array} rawValues The raw values | ||
* @returns {number|bigint|undefined} The value | ||
* | ||
* @private | ||
*/ | ||
MetadataPicking.decodeMetadataValues = function (classProperty, rawValues) { | ||
const componentType = classProperty.componentType; | ||
const dataView = new DataView( | ||
rawValues.buffer, | ||
rawValues.byteOffset, | ||
rawValues.byteLength | ||
); | ||
if (classProperty.isArray) { | ||
const arrayLength = classProperty.arrayLength; | ||
const result = Array(arrayLength); | ||
for (let i = 0; i < arrayLength; i++) { | ||
const element = MetadataPicking.decodeMetadataValue( | ||
componentType, | ||
dataView, | ||
i | ||
); | ||
if (classProperty.normalized) { | ||
result[i] = element / 255.0; | ||
} else { | ||
result[i] = element; | ||
} | ||
} | ||
return result; | ||
} | ||
const value = MetadataPicking.decodeMetadataValue(componentType, dataView, 0); | ||
if (classProperty.normalized) { | ||
return value / 255.0; | ||
} | ||
return value; | ||
}; | ||
|
||
export default Object.freeze(MetadataPicking); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'll need a more precise regex here. There could be 2 defines starting with the same word, e.g.,
PICKING
andPICKING_VOXEL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is now checking whether the first "token" in that define directive is the
defineName
(where "token" is everything before the first whitespace, trimmed).