[IR] adding OpNot and fixing access chain handling
Mirror Gitea refs to GitHub / mirror (push) Successful in 16s
Test / build_and_test (push) Failing after 1m42s
Build / build (push) Successful in 5m2s

This commit is contained in:
2026-08-30 02:21:27 +02:00
parent a6c6295d87
commit 9c4b074622
3 changed files with 368 additions and 17 deletions
+73 -4
View File
@@ -34,6 +34,11 @@ pub const ValidationError = error{
WrongReturnType,
};
const IntegerShape = struct {
bits: u16,
components: u8,
};
pub const Error = ValidationError || std.mem.Allocator.Error;
pub fn validate(module: *const module_ir.Module) Error!void {
@@ -227,13 +232,37 @@ fn validateOperation(module: *const module_ir.Module, function_id: ids.FunctionI
switch (instruction.operation) {
.unary => |op| {
const operand_type = try operandType(module, function_id, op.operand);
const operand_type =
try operandType(module, function_id, op.operand);
if (result_type == null)
return ValidationError.WrongResultPresence;
const result =
result_type orelse return ValidationError.WrongResultPresence;
if (result_type.? != operand_type)
switch (op.opcode) {
.bitwise_not => {
if (!isIntegerScalarOrVector(module, operand_type))
return ValidationError.WrongOperandType;
if (!isIntegerScalarOrVector(module, result))
return ValidationError.WrongResultType;
if (!haveSameIntegerShape(module, operand_type, result))
return ValidationError.WrongResultType;
},
.logical_not => {
if (!isBoolean(module, operand_type))
return ValidationError.WrongOperandType;
if (!isBoolean(module, result))
return ValidationError.WrongResultType;
},
.negate => {
if (result != operand_type)
return ValidationError.WrongResultType;
},
}
},
.binary => |op| {
const lhs_type = try operandType(module, function_id, op.lhs);
@@ -533,6 +562,46 @@ fn isArrayLengthResultType(module: *const module_ir.Module, type_id: ids.TypeId)
};
}
fn integerShape(module: *const module_ir.Module, type_id: ids.TypeId) ?IntegerShape {
const ty = module.types.get(type_id) orelse return null;
return switch (ty.*) {
.integer => |integer| .{
.bits = integer.bits,
.components = 1,
},
.vector => |vector| blk: {
const element =
module.types.get(vector.element_type) orelse return null;
const integer = switch (element.*) {
.integer => |integer| integer,
else => return null,
};
break :blk .{
.bits = integer.bits,
.components = vector.length,
};
},
else => null,
};
}
fn isIntegerScalarOrVector(module: *const module_ir.Module, type_id: ids.TypeId) bool {
return integerShape(module, type_id) != null;
}
fn haveSameIntegerShape(module: *const module_ir.Module, lhs: ids.TypeId, rhs: ids.TypeId) bool {
const lhs_shape = integerShape(module, lhs) orelse return false;
const rhs_shape = integerShape(module, rhs) orelse return false;
return lhs_shape.bits == rhs_shape.bits and
lhs_shape.components == rhs_shape.components;
}
fn targetsBlock(terminator: module_ir.Terminator, target: ids.BlockId) bool {
return switch (terminator) {
.branch => |edge| edge.target == target,
+229 -10
View File
@@ -69,6 +69,16 @@ const BufferAddress = struct {
pointee_type: u32,
};
const CompositeAddress = struct {
root: union(enum) {
local: usize,
interface: ir.id.InterfaceVariableId,
},
root_type: u32,
pointee_type: u32,
indices: []const u32,
};
const LocalVariable = struct {
spv_id: u32,
type: ir.id.TypeId,
@@ -99,6 +109,7 @@ const Context = struct {
interfaces: []?ir.id.InterfaceVariableId,
resources: []?ir.id.ResourceId,
buffer_addresses: []?BufferAddress,
composite_addresses: []?CompositeAddress,
member_offsets: std.ArrayList(MemberOffset) = .empty,
phi_infos: std.ArrayList(PhiInfo) = .empty,
@@ -391,6 +402,11 @@ const Context = struct {
return self.buffer_addresses[index];
}
fn compositeAddress(self: *const Context, spv_id: u32) TranslationError!?CompositeAddress {
const index = try self.idIndex(spv_id);
return self.composite_addresses[index];
}
fn localIndex(self: *const Context, spv_id: u32) TranslationError!?usize {
const index = try self.idIndex(spv_id);
return self.local_indices[index];
@@ -437,6 +453,7 @@ pub fn instantiate(allocator: std.mem.Allocator, source: *const SourceModule, op
.interfaces = try allocOptional(ir.id.InterfaceVariableId, scratch, bound),
.resources = try allocOptional(ir.id.ResourceId, scratch, bound),
.buffer_addresses = try allocOptional(BufferAddress, scratch, bound),
.composite_addresses = try allocOptional(CompositeAddress, scratch, bound),
.local_indices = try allocOptional(usize, scratch, bound),
.block_local_inputs = try allocOptional(ir.id.ValueId, scratch, 0),
.block_local_outputs = try allocOptional(ir.id.ValueId, scratch, 0),
@@ -992,6 +1009,23 @@ fn translateInstruction(context: *Context, block: ir.id.BlockId, instruction: Pa
},
}, context.nameOf(operands[1]))).?;
try context.setValue(operands[1], result);
} else if (try context.compositeAddress(operands[2])) |address| {
const composite = switch (address.root) {
.local => |local_index| context.current_locals[local_index] orelse return TranslationError.InvalidInstruction,
.interface => |variable| (try context.builder.appendInstruction(
block,
try context.translateType(address.root_type),
.{ .load_interface = .{ .variable = variable } },
null,
)).?,
};
const result = (try context.builder.appendInstruction(block, result_type, .{
.composite_extract = .{
.composite = composite,
.indices = address.indices,
},
}, context.nameOf(operands[1]))).?;
try context.setValue(operands[1], result);
} else {
const result = (try context.builder.appendInstruction(block, result_type, .{
.load_interface = .{ .variable = try context.interfaceVariable(operands[2]) },
@@ -1016,6 +1050,8 @@ fn translateInstruction(context: *Context, block: ir.id.BlockId, instruction: Pa
.value = value,
},
}, null);
} else if (try context.compositeAddress(operands[0]) != null) {
return TranslationError.UnsupportedOpcode;
} else {
_ = try context.builder.appendInstruction(block, null, .{
.store_interface = .{
@@ -1029,15 +1065,32 @@ fn translateInstruction(context: *Context, block: ir.id.BlockId, instruction: Pa
.s_negate,
.f_negate,
.logical_not,
.not,
=> {
try expectOperandCount(operands, 3);
const result = (try context.builder.appendInstruction(block, try context.translateType(operands[0]), .{
const opcode: ir.instruction.UnaryOpcode = switch (instruction.opcode) {
.s_negate,
.f_negate,
=> .negate,
.logical_not => .logical_not,
.not => .bitwise_not,
else => unreachable,
};
const result = (try context.builder.appendInstruction(
block,
try context.translateType(operands[0]),
.{
.unary = .{
.opcode = if (instruction.opcode == .logical_not) .logical_not else .negate,
.opcode = opcode,
.operand = try context.resolveValue(operands[2]),
},
}, context.nameOf(operands[1]))).?;
},
context.nameOf(operands[1]),
)).?;
try context.setValue(operands[1], result);
},
@@ -1064,11 +1117,12 @@ fn translateInstruction(context: *Context, block: ir.id.BlockId, instruction: Pa
=> {
try expectOperandCount(operands, 4);
const result = (try context.builder.appendInstruction(block, try context.translateType(operands[0]), .{
const result_type = try context.translateType(operands[0]);
const result = (try context.builder.appendInstruction(block, result_type, .{
.binary = .{
.opcode = translateBinaryOpcode(instruction.opcode),
.lhs = try context.resolveValue(operands[2]),
.rhs = try context.resolveValue(operands[3]),
.lhs = try coerceIntegerSignedness(context, block, try context.resolveValue(operands[2]), result_type),
.rhs = try coerceIntegerSignedness(context, block, try context.resolveValue(operands[3]), result_type),
},
}, context.nameOf(operands[1]))).?;
@@ -1156,9 +1210,9 @@ fn translateInstruction(context: *Context, block: ir.id.BlockId, instruction: Pa
else => {
if (std.enums.tagName(spirv.Opcode, instruction.opcode)) |opcode| {
std.log.scoped(.spirv_translator).err("unsupported opcode {s}", .{opcode});
std.log.scoped(.spirv_translator).err("unsupported opcode: '{s}'", .{opcode});
} else {
std.log.scoped(.spirv_translator).err("unsupported opcode {d}", .{instruction.opcode});
std.log.scoped(.spirv_translator).err("unsupported opcode: {d}", .{instruction.opcode});
}
return TranslationError.UnsupportedOpcode;
},
@@ -1169,7 +1223,13 @@ fn translateAccessChain(context: *Context, block: ir.id.BlockId, operands: []con
if (operands.len < 4)
return TranslationError.InvalidInstruction;
const base = (try context.bufferAddress(operands[2])) orelse return TranslationError.UnsupportedOpcode;
if (try context.bufferAddress(operands[2])) |base|
return translateBufferAccessChain(context, block, operands, base);
try translateCompositeAccessChain(context, operands);
}
fn translateBufferAccessChain(context: *Context, block: ir.id.BlockId, operands: []const u32, base: BufferAddress) !void {
var current_type = base.pointee_type;
var byte_offset = base.byte_offset;
@@ -1189,7 +1249,7 @@ fn translateAccessChain(context: *Context, block: ir.id.BlockId, operands: []con
current_type = type_definition.operands[member + 1];
},
.type_array, .type_runtime_array => {
try expectOperandCount(type_definition.operands, 3);
try expectOperandCount(type_definition.operands, if (type_definition.opcode == .type_array) 3 else 2);
const stride = context.decorations[try context.idIndex(current_type)].array_stride orelse return TranslationError.InvalidInstruction;
const index = try unsignedOffsetValue(context, block, index_id);
const stride_value = try context.builder.internConstant(try unsigned32Type(context), .{ .integer_bits = stride });
@@ -1224,6 +1284,81 @@ fn translateAccessChain(context: *Context, block: ir.id.BlockId, operands: []con
};
}
fn translateCompositeAccessChain(context: *Context, operands: []const u32) !void {
var address: CompositeAddress = if (try context.compositeAddress(operands[2])) |base|
base
else if (try context.localIndex(operands[2])) |local_index| blk: {
const pointee_type = try variablePointeeType(context, operands[2]);
break :blk .{
.root = .{ .local = local_index },
.root_type = pointee_type,
.pointee_type = pointee_type,
.indices = &.{},
};
} else blk: {
const pointee_type = try variablePointeeType(context, operands[2]);
break :blk .{
.root = .{ .interface = try context.interfaceVariable(operands[2]) },
.root_type = pointee_type,
.pointee_type = pointee_type,
.indices = &.{},
};
};
var indices = std.ArrayList(u32).empty;
defer indices.deinit(context.scratch);
try indices.appendSlice(context.scratch, address.indices);
for (operands[3..]) |index_id| {
const type_definition = context.type_defs[try context.idIndex(address.pointee_type)] orelse return TranslationError.MissingDefinition;
const index = try constantIndex(context, index_id);
address.pointee_type = switch (type_definition.opcode) {
.type_struct => blk: {
if (index + 1 >= type_definition.operands.len)
return TranslationError.InvalidInstruction;
break :blk type_definition.operands[index + 1];
},
.type_vector, .type_matrix => blk: {
try expectOperandCount(type_definition.operands, 3);
if (index >= type_definition.operands[2])
return TranslationError.InvalidInstruction;
break :blk type_definition.operands[1];
},
.type_array => blk: {
try expectOperandCount(type_definition.operands, 3);
const length = try constantIndex(context, type_definition.operands[2]);
if (index >= length)
return TranslationError.InvalidInstruction;
break :blk type_definition.operands[1];
},
else => return TranslationError.UnsupportedType,
};
try indices.append(context.scratch, index);
}
const result_pointer = context.type_defs[try context.idIndex(operands[0])] orelse return TranslationError.MissingDefinition;
if (result_pointer.opcode != .type_pointer)
return TranslationError.InvalidInstruction;
try expectOperandCount(result_pointer.operands, 3);
if (result_pointer.operands[2] != address.pointee_type)
return TranslationError.InvalidInstruction;
const result_index = try context.idIndex(operands[1]);
if (context.composite_addresses[result_index] != null)
return TranslationError.DuplicateId;
address.indices = try context.scratch.dupe(u32, indices.items);
context.composite_addresses[result_index] = address;
}
fn variablePointeeType(context: *Context, spv_id: u32) !u32 {
const variable = context.variable_defs[try context.idIndex(spv_id)] orelse return TranslationError.MissingDefinition;
const pointer = context.type_defs[try context.idIndex(variable.operands[0])] orelse return TranslationError.MissingDefinition;
if (pointer.opcode != .type_pointer)
return TranslationError.InvalidInstruction;
try expectOperandCount(pointer.operands, 3);
return pointer.operands[2];
}
fn translateArrayLength(context: *Context, block: ir.id.BlockId, operands: []const u32) !void {
try expectOperandCount(operands, 4);
@@ -1309,6 +1444,40 @@ fn unsigned32Type(context: *Context) !ir.id.TypeId {
return context.builder.internType(.{ .integer = .{ .bits = 32, .signedness = .unsigned } });
}
fn coerceIntegerSignedness(context: *Context, block: ir.id.BlockId, value: ir.id.ValueId, target_type: ir.id.TypeId) !ir.id.ValueId {
const source_type = context.module.typeOf(value) orelse return TranslationError.InvalidId;
if (source_type == target_type)
return value;
const source_shape = integerTypeShape(context, source_type) orelse return TranslationError.InvalidInstruction;
const target_shape = integerTypeShape(context, target_type) orelse return TranslationError.InvalidInstruction;
if (!std.meta.eql(source_shape, target_shape))
return TranslationError.InvalidInstruction;
return (try context.builder.appendInstruction(block, target_type, .{ .bitcast = value }, null)).?;
}
const IntegerTypeShape = struct {
bits: u16,
components: u8,
};
fn integerTypeShape(context: *const Context, type_id: ir.id.TypeId) ?IntegerTypeShape {
const ty = context.module.types.get(type_id) orelse return null;
return switch (ty.*) {
.integer => |integer| .{ .bits = integer.bits, .components = 1 },
.vector => |vector| blk: {
const element_type = context.module.types.get(vector.element_type) orelse return null;
const integer = switch (element_type.*) {
.integer => |integer| integer,
else => return null,
};
break :blk .{ .bits = integer.bits, .components = vector.length };
},
else => null,
};
}
fn unsignedOffsetValue(context: *Context, block: ir.id.BlockId, spv_id: u32) !ir.id.ValueId {
const value = try context.resolveValue(spv_id);
const type_id = context.module.typeOf(value) orelse return TranslationError.InvalidId;
@@ -1795,6 +1964,56 @@ test "SPIR-V: decorated vertex interfaces and load-store operations" {
try std.testing.expect(std.mem.indexOf(u8, text, "store_interface @out_color") != null);
}
test "SPIR-V: access chains into interface vectors and promoted local vectors" {
const assembly =
\\OpCapability Shader
\\OpMemoryModel Logical GLSL450
\\OpEntryPoint GLCompute %main "main" %global_id
\\OpExecutionMode %main LocalSize 1 1 1
\\OpName %global_y "global_y"
\\OpName %local_z "local_z"
\\OpName %signed_one "signed_one"
\\OpDecorate %global_id BuiltIn GlobalInvocationId
\\%void = OpTypeVoid
\\%uint = OpTypeInt 32 0
\\%int = OpTypeInt 32 1
\\%vec3 = OpTypeVector %uint 3
\\%ptr_input_vec3 = OpTypePointer Input %vec3
\\%ptr_input_uint = OpTypePointer Input %uint
\\%ptr_function_vec3 = OpTypePointer Function %vec3
\\%ptr_function_uint = OpTypePointer Function %uint
\\%fn_void = OpTypeFunction %void
\\%one = OpConstant %uint 1
\\%two = OpConstant %uint 2
\\%signed_one = OpConstant %int 1
\\%global_id = OpVariable %ptr_input_vec3 Input
\\%main = OpFunction %void None %fn_void
\\ %entry = OpLabel
\\ %local = OpVariable %ptr_function_vec3 Function
\\ %global = OpLoad %vec3 %global_id
\\ OpStore %local %global
\\ %global_y_ptr = OpAccessChain %ptr_input_uint %global_id %one
\\ %global_y = OpLoad %uint %global_y_ptr
\\ %local_z_ptr = OpAccessChain %ptr_function_uint %local %two
\\ %local_z = OpLoad %uint %local_z_ptr
\\ %sum = OpIAdd %uint %global_y %local_z
\\ %increment = OpIAdd %uint %sum %signed_one
\\ OpReturn
\\OpFunctionEnd
;
const words = try assembleSpirv(std.testing.allocator, assembly);
defer std.testing.allocator.free(words);
var module = try translate(std.testing.allocator, words, .{ .entry_point = "main" });
defer module.deinit();
const text = try ir.printer.allocPrint(std.testing.allocator, &module);
defer std.testing.allocator.free(text);
try std.testing.expect(std.mem.indexOf(u8, text, "%global_y: u32 = composite_extract") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "%local_z: u32 = composite_extract") != null);
try std.testing.expect(std.mem.indexOf(u8, text, "bitcast %signed_one") != null);
}
test "SPIR-V: storage buffers and promoted function locals" {
const assembly =
\\OpCapability Shader
+63
View File
@@ -6,6 +6,69 @@ const PciInfo = struct {
/// Not a hashmap as they need runtime allocations
pub const map = [_]PciInfo{
.{ .id = 0x0a02, .name = "Intel(R) HD Graphics (Haswell-ULT GT1)", .is_discrete = false },
.{ .id = 0x0a06, .name = "Intel(R) HD Graphics (Haswell-ULT GT1)", .is_discrete = false },
.{ .id = 0x0a0a, .name = "Intel(R) HD Graphics (Haswell-ULT GT1)", .is_discrete = false },
.{ .id = 0x0a0b, .name = "Intel(R) HD Graphics (Haswell-ULT GT1)", .is_discrete = false },
.{ .id = 0x0a0e, .name = "Intel(R) HD Graphics (Haswell-ULX GT1)", .is_discrete = false },
.{ .id = 0x0402, .name = "Intel(R) HD Graphics (Haswell GT1)", .is_discrete = false },
.{ .id = 0x0406, .name = "Intel(R) HD Graphics (Haswell GT1)", .is_discrete = false },
.{ .id = 0x040a, .name = "Intel(R) HD Graphics (Haswell GT1)", .is_discrete = false },
.{ .id = 0x040b, .name = "Intel(R) HD Graphics (Haswell GT1)", .is_discrete = false },
.{ .id = 0x040e, .name = "Intel(R) HD Graphics (Haswell GT1)", .is_discrete = false },
.{ .id = 0x0c02, .name = "Intel(R) HD Graphics (Haswell-SDV GT1)", .is_discrete = false },
.{ .id = 0x0c06, .name = "Intel(R) HD Graphics (Haswell-SDV GT1)", .is_discrete = false },
.{ .id = 0x0c0a, .name = "Intel(R) HD Graphics (Haswell-SDV GT1)", .is_discrete = false },
.{ .id = 0x0c0b, .name = "Intel(R) HD Graphics (Haswell-SDV GT1)", .is_discrete = false },
.{ .id = 0x0c0e, .name = "Intel(R) HD Graphics (Haswell-SDV GT1)", .is_discrete = false },
.{ .id = 0x0d02, .name = "Intel(R) HD Graphics (Haswell-CRW GT1)", .is_discrete = false },
.{ .id = 0x0d06, .name = "Intel(R) HD Graphics (Haswell-CRW GT1)", .is_discrete = false },
.{ .id = 0x0d0a, .name = "Intel(R) HD Graphics (Haswell-CRW GT1)", .is_discrete = false },
.{ .id = 0x0d0b, .name = "Intel(R) HD Graphics (Haswell-CRW GT1)", .is_discrete = false },
.{ .id = 0x0d0e, .name = "Intel(R) HD Graphics (Haswell-CRW GT1)", .is_discrete = false },
.{ .id = 0x0a12, .name = "Intel(R) HD Graphics (Haswell-ULT GT2)", .is_discrete = false },
.{ .id = 0x0a16, .name = "Intel(R) HD Graphics 4400 (Haswell-ULT GT2)", .is_discrete = false },
.{ .id = 0x0a1a, .name = "Intel(R) HD Graphics (Haswell-ULT GT2)", .is_discrete = false },
.{ .id = 0x0a1b, .name = "Intel(R) HD Graphics (Haswell-ULT GT2)", .is_discrete = false },
.{ .id = 0x0a1e, .name = "Intel(R) HD Graphics 4200 (Haswell-ULX GT2)", .is_discrete = false },
.{ .id = 0x0412, .name = "Intel(R) HD Graphics 4600 (Haswell GT2)", .is_discrete = false },
.{ .id = 0x0416, .name = "Intel(R) HD Graphics 4600 (Haswell GT2)", .is_discrete = false },
.{ .id = 0x041a, .name = "Intel(R) HD Graphics P4600/P4700 (Haswell GT2)", .is_discrete = false },
.{ .id = 0x041b, .name = "Intel(R) HD Graphics (Haswell GT2)", .is_discrete = false },
.{ .id = 0x041e, .name = "Intel(R) HD Graphics 4400 (Haswell GT2)", .is_discrete = false },
.{ .id = 0x0c12, .name = "Intel(R) HD Graphics (Haswell-SDV GT2)", .is_discrete = false },
.{ .id = 0x0c16, .name = "Intel(R) HD Graphics (Haswell-SDV GT2)", .is_discrete = false },
.{ .id = 0x0c1a, .name = "Intel(R) HD Graphics (Haswell-SDV GT2)", .is_discrete = false },
.{ .id = 0x0c1b, .name = "Intel(R) HD Graphics (Haswell-SDV GT2)", .is_discrete = false },
.{ .id = 0x0c1e, .name = "Intel(R) HD Graphics (Haswell-SDV GT2)", .is_discrete = false },
.{ .id = 0x0d12, .name = "Intel(R) HD Graphics 4600 (Haswell-CRW GT2)", .is_discrete = false },
.{ .id = 0x0d16, .name = "Intel(R) HD Graphics (Haswell-CRW GT2)", .is_discrete = false },
.{ .id = 0x0d1a, .name = "Intel(R) HD Graphics (Haswell-CRW GT2)", .is_discrete = false },
.{ .id = 0x0d1b, .name = "Intel(R) HD Graphics (Haswell-CRW GT2)", .is_discrete = false },
.{ .id = 0x0d1e, .name = "Intel(R) HD Graphics (Haswell-CRW GT2)", .is_discrete = false },
.{ .id = 0x0a22, .name = "Intel(R) HD Graphics (Haswell-ULT GT3)", .is_discrete = false },
.{ .id = 0x0a26, .name = "Intel(R) HD Graphics 5000 (Haswell-ULT GT3)", .is_discrete = false },
.{ .id = 0x0a2a, .name = "Intel(R) HD Graphics (Haswell-ULT GT3)", .is_discrete = false },
.{ .id = 0x0a2b, .name = "Intel(R) HD Graphics (Haswell-ULT GT3)", .is_discrete = false },
.{ .id = 0x0a2e, .name = "Intel(R) Iris(R) Graphics 5100 (Haswell-ULT GT3)", .is_discrete = false },
.{ .id = 0x0422, .name = "Intel(R) HD Graphics (Haswell GT3)", .is_discrete = false },
.{ .id = 0x0426, .name = "Intel(R) HD Graphics (Haswell GT3)", .is_discrete = false },
.{ .id = 0x042a, .name = "Intel(R) HD Graphics (Haswell GT3)", .is_discrete = false },
.{ .id = 0x042b, .name = "Intel(R) HD Graphics (Haswell GT3)", .is_discrete = false },
.{ .id = 0x042e, .name = "Intel(R) HD Graphics (Haswell GT3)", .is_discrete = false },
.{ .id = 0x0c22, .name = "Intel(R) HD Graphics (Haswell-SDV GT3)", .is_discrete = false },
.{ .id = 0x0c26, .name = "Intel(R) HD Graphics (Haswell-SDV GT3)", .is_discrete = false },
.{ .id = 0x0c2a, .name = "Intel(R) HD Graphics (Haswell-SDV GT3)", .is_discrete = false },
.{ .id = 0x0c2b, .name = "Intel(R) HD Graphics (Haswell-SDV GT3)", .is_discrete = false },
.{ .id = 0x0c2e, .name = "Intel(R) HD Graphics (Haswell-SDV GT3)", .is_discrete = false },
.{ .id = 0x0d22, .name = "Intel(R) Iris(R) Pro Graphics 5200 (Haswell-CRW GT3)", .is_discrete = false },
.{ .id = 0x0d26, .name = "Intel(R) Iris(R) Pro Graphics P5200 (Haswell-CRW GT3)", .is_discrete = false },
.{ .id = 0x0d2a, .name = "Intel(R) HD Graphics (Haswell-CRW GT3)", .is_discrete = false },
.{ .id = 0x0d2b, .name = "Intel(R) HD Graphics (Haswell-CRW GT3)", .is_discrete = false },
.{ .id = 0x0d2e, .name = "Intel(R) HD Graphics (Haswell-CRW GT3)", .is_discrete = false },
.{ .id = 0x1602, .name = "Intel(R) HD Graphics (Broadwell-U)", .is_discrete = false },
.{ .id = 0x1606, .name = "Intel(R) HD Graphics (Broadwell-U GT1)", .is_discrete = false },
.{ .id = 0x160a, .name = "Intel(R) HD Graphics (Broadwell-U)", .is_discrete = false },