public inbox for igt-dev@lists.freedesktop.org
 help / color / mirror / Atom feed
From: Harry Wentland <harry.wentland@amd.com>
To: <igt-dev@lists.freedesktop.org>
Cc: Harry Wentland <harry.wentland@amd.com>
Subject: [RFC PATCH v2 08/11] tests/kms_colorop: Add CSC FF colorop tests
Date: Mon, 30 Mar 2026 11:35:15 -0400	[thread overview]
Message-ID: <20260330153518.99898-9-harry.wentland@amd.com> (raw)
In-Reply-To: <20260330153518.99898-1-harry.wentland@amd.com>

Add comprehensive test coverage for CSC Fixed-Function colorops:

Test infrastructure:
- CSC FF colorop definitions for various encoding/range combinations
- Support for BT.601, BT.709, and BT.2020 color standards
- Limited and full range YUV testing

Test cases:
- NV12 (8-bit YUV 4:2:0) CSC tests
- P010 (10-bit YUV 4:2:0) CSC tests
- BT.709, BT.601, and BT.2020 encoding tests
- Limited and full range tests
- CSC combined with additional colorops (sRGB EOTF, 3D LUT, CTM)
- Organized into separate RGB and YUV test groups

The tests verify that CSC FF colorops correctly convert YUV input to
RGB output by comparing hardware CSC results with software reference
transformations using the igt_color_encoding matrix library.

Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
---
 tests/kms_colorop.c | 256 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 205 insertions(+), 51 deletions(-)

diff --git a/tests/kms_colorop.c b/tests/kms_colorop.c
index ead8d9f49c22..fbf254b694bb 100644
--- a/tests/kms_colorop.c
+++ b/tests/kms_colorop.c
@@ -20,50 +20,95 @@
  * SUBTEST: check_plane_colorop_ids
  * Description: Verify that all igt_colorop_t IDs are unique across planes
  *
- * SUBTEST: plane-%s-%s
- * Description: Tests DRM colorop properties on a plane
+ * SUBTEST: plane-XR24-XR24-bypass
+ * SUBTEST: plane-XR24-XR24-srgb_eotf
+ * SUBTEST: plane-XR24-XR24-srgb_inv_eotf
+ * SUBTEST: plane-XR24-XR24-srgb_eotf-srgb_inv_eotf
+ * SUBTEST: plane-XR24-XR24-srgb_eotf-srgb_inv_eotf-srgb_eotf
+ * SUBTEST: plane-XR24-XR24-srgb_inv_eotf_lut
+ * SUBTEST: plane-XR24-XR24-srgb_inv_eotf_lut-srgb_eotf_lut
+ * SUBTEST: plane-XR24-XR24-bt2020_inv_oetf
+ * SUBTEST: plane-XR24-XR24-bt2020_oetf
+ * SUBTEST: plane-XR24-XR24-bt2020_inv_oetf-bt2020_oetf
+ * SUBTEST: plane-XR24-XR24-pq_eotf
+ * SUBTEST: plane-XR24-XR24-pq_inv_eotf
+ * SUBTEST: plane-XR24-XR24-pq_eotf-pq_inv_eotf
+ * SUBTEST: plane-XR24-XR24-pq_125_eotf
+ * SUBTEST: plane-XR24-XR24-pq_125_inv_eotf
+ * SUBTEST: plane-XR24-XR24-pq_125_eotf-pq_125_inv_eotf
+ * SUBTEST: plane-XR24-XR24-pq_125_eotf-pq_125_inv_eotf-pq_125_eotf
+ * SUBTEST: plane-XR24-XR24-gamma_2_2
+ * SUBTEST: plane-XR24-XR24-gamma_2_2-gamma_2_2_inv
+ * SUBTEST: plane-XR24-XR24-gamma_2_2-gamma_2_2_inv-gamma_2_2
+ * SUBTEST: plane-XR24-XR24-ctm_3x4_50_desat
+ * SUBTEST: plane-XR24-XR24-ctm_3x4_overdrive
+ * SUBTEST: plane-XR24-XR24-ctm_3x4_oversaturate
+ * SUBTEST: plane-XR24-XR24-ctm_3x4_bt709_enc
+ * SUBTEST: plane-XR24-XR24-ctm_3x4_bt709_dec
+ * SUBTEST: plane-XR24-XR24-ctm_3x4_bt709_enc_dec
+ * SUBTEST: plane-XR24-XR24-ctm_3x4_bt709_dec_enc
+ * SUBTEST: plane-XR24-XR24-multiply_125
+ * SUBTEST: plane-XR24-XR24-multiply_inv_125
+ * SUBTEST: plane-XR24-XR24-3dlut_17_12_rgb
+ * SUBTEST: plane-XR30-XR30-bypass
+ * SUBTEST: plane-XR30-XR30-srgb_eotf
+ * SUBTEST: plane-XR30-XR30-srgb_inv_eotf
+ * SUBTEST: plane-XR30-XR30-srgb_eotf-srgb_inv_eotf
+ * SUBTEST: plane-XR30-XR30-srgb_eotf-srgb_inv_eotf-srgb_eotf
+ * SUBTEST: plane-XR30-XR30-srgb_inv_eotf_lut
+ * SUBTEST: plane-XR30-XR30-srgb_inv_eotf_lut-srgb_eotf_lut
+ * SUBTEST: plane-XR30-XR30-bt2020_inv_oetf
+ * SUBTEST: plane-XR30-XR30-bt2020_oetf
+ * SUBTEST: plane-XR30-XR30-bt2020_inv_oetf-bt2020_oetf
+ * SUBTEST: plane-XR30-XR30-pq_eotf
+ * SUBTEST: plane-XR30-XR30-pq_inv_eotf
+ * SUBTEST: plane-XR30-XR30-pq_eotf-pq_inv_eotf
+ * SUBTEST: plane-XR30-XR30-pq_125_eotf
+ * SUBTEST: plane-XR30-XR30-pq_125_inv_eotf
+ * SUBTEST: plane-XR30-XR30-pq_125_eotf-pq_125_inv_eotf
+ * SUBTEST: plane-XR30-XR30-pq_125_eotf-pq_125_inv_eotf-pq_125_eotf
+ * SUBTEST: plane-XR30-XR30-gamma_2_2
+ * SUBTEST: plane-XR30-XR30-gamma_2_2-gamma_2_2_inv
+ * SUBTEST: plane-XR30-XR30-gamma_2_2-gamma_2_2_inv-gamma_2_2
+ * SUBTEST: plane-XR30-XR30-ctm_3x4_50_desat
+ * SUBTEST: plane-XR30-XR30-ctm_3x4_overdrive
+ * SUBTEST: plane-XR30-XR30-ctm_3x4_oversaturate
+ * SUBTEST: plane-XR30-XR30-ctm_3x4_bt709_enc
+ * SUBTEST: plane-XR30-XR30-ctm_3x4_bt709_dec
+ * SUBTEST: plane-XR30-XR30-ctm_3x4_bt709_enc_dec
+ * SUBTEST: plane-XR30-XR30-ctm_3x4_bt709_dec_enc
+ * SUBTEST: plane-XR30-XR30-multiply_125
+ * SUBTEST: plane-XR30-XR30-multiply_inv_125
+ * SUBTEST: plane-XR30-XR30-3dlut_17_12_rgb
+ * Description: Tests DRM colorop properties on RGB formats
  * Driver requirement: amdgpu
  * Functionality: kms_core
  * Mega feature: General Display Features
  * Test category: functionality test
  *
- * arg[1]:
- *
- * @XR24-XR24:				XRGB8888 framebuffer and writeback buffer
- * @XR30-XR30:				XRGB2101010 framebuffer and writeback buffer
- *
- * arg[2]:
- *
- * @bypass:				Bypass Color Pipeline
- * @srgb_eotf:				sRGB EOTF
- * @srgb_inv_eotf:			sRGB Inverse EOTF
- * @srgb_eotf-srgb_inv_eotf:		sRGB EOTF -> sRGB Inverse EOTF
- * @srgb_eotf-srgb_inv_eotf-srgb_eotf:  sRGB EOTF -> sRGB Inverse EOTF -> sRGB EOTF
- * @srgb_inv_eotf_lut:			sRGB Inverse EOTF Custom LUT
- * @srgb_inv_eotf_lut-srgb_eotf_lut:	sRGB Inverse EOTF Custom LUT -> sRGB EOTF Custom LUT
- * @bt2020_inv_oetf:			BT.2020 Inverse OETF
- * @bt2020_oetf:			BT.2020 OETF
- * @bt2020_inv_oetf-bt2020_oetf:	BT.2020 Inverse OETF > BT.2020 OETF
- * @pq_eotf:				PQ EOTF
- * @pq_inv_eotf:			PQ Inverse EOTF
- * @pq_eotf-pq_inv_eotf:		PQ EOTF -> PQ Inverse EOTF
- * @pq_125_eotf:			PQ EOTF for [0.0, 125.0] optical range
- * @pq_125_inv_eotf:			PQ Inverse EOTF for [0.0, 125.0] optical range
- * @pq_125_eotf-pq_125_inv_eotf:	PQ EOTF -> PQ Inverse EOTF with [0.0, 125.0] optical range
- * @pq_125_eotf-pq_125_inv_eotf-pq_125_eotf: PQ EOTF -> PQ Inverse EOTF -> PQ EOTF with [0.0, 125.0] optical range
- * @gamma_2_2:				Gamma 2.2
- * @gamma_2_2-gamma_2_2_inv:		Gamma 2.2 -> Gamma 2.2 Inverse
- * @gamma_2_2-gamma_2_2_inv-gamma_2_2:	Gamma 2.2 -> Gamma 2.2 Inverse -> Gamma 2.2
- * @ctm_3x4_50_desat:			3x4 matrix doing a 50% desaturation
- * @ctm_3x4_overdrive:			3x4 matrix overdring all values by 50%
- * @ctm_3x4_oversaturate:		3x4 matrix oversaturating values
- * @ctm_3x4_bt709_enc:			BT709 encoding matrix
- * @ctm_3x4_bt709_dec:			BT709 decoding matrix
- * @ctm_3x4_bt709_enc_dec:		BT709 encoding matrix, followed by decoding matrix
- * @ctm_3x4_bt709_dec_enc:		BT709 decoding matrix, followed by encoding matrix
- * @multiply_125:			Multiplier by 125
- * @multiply_inv_125:			Multiplier by inverse of 125
- * @3dlut_17_12_rgb:			3D LUT with length 17, color depth 12, and traversal order = RGB
+ * SUBTEST: plane-NV12-XR24-csc_bt709_limited
+ * SUBTEST: plane-NV12-XR24-csc_bt709_full
+ * SUBTEST: plane-NV12-XR24-csc_bt601_limited
+ * SUBTEST: plane-NV12-XR24-csc_bt2020_limited
+ * SUBTEST: plane-NV12-XR24-csc_bt709_limited-srgb_eotf
+ * SUBTEST: plane-NV12-XR24-csc_bt601_limited-srgb_eotf
+ * SUBTEST: plane-NV12-XR24-csc_bt709_limited-3dlut_17_12_rgb
+ * SUBTEST: plane-NV12-XR24-csc_bt709_limited-ctm_3x4_50_desat
+ * SUBTEST: plane-NV12-XR24-csc_bt709_limited-srgb_eotf-ctm_3x4_50_desat
+ * SUBTEST: plane-P010-XR30-csc_bt709_limited
+ * SUBTEST: plane-P010-XR30-csc_bt709_full
+ * SUBTEST: plane-P010-XR30-csc_bt601_limited
+ * SUBTEST: plane-P010-XR30-csc_bt2020_limited
+ * SUBTEST: plane-P010-XR30-csc_bt709_limited-srgb_eotf
+ * SUBTEST: plane-P010-XR30-csc_bt601_limited-srgb_eotf
+ * SUBTEST: plane-P010-XR30-csc_bt709_limited-3dlut_17_12_rgb
+ * SUBTEST: plane-P010-XR30-csc_bt709_limited-ctm_3x4_50_desat
+ * SUBTEST: plane-P010-XR30-csc_bt709_limited-srgb_eotf-ctm_3x4_50_desat
+ * Description: Tests DRM colorop properties on YUV formats
+ * Driver requirement: amdgpu
+ * Functionality: kms_core
+ * Mega feature: General Display Features
+ * Test category: functionality test
  *
  */
 
@@ -373,10 +418,11 @@ static const struct option long_options[] = {
 int igt_main_args("d", long_options, help_str, opt_handler, NULL)
 {
 
+	/* RGB tests - for RGB input formats only */
 	struct {
 		kms_colorop_t *colorops[MAX_COLOROPS];
 		const char *name;
-	} tests[] = {
+	} tests_rgb[] = {
 		{ { NULL }, "bypass" },
 		{ { &kms_colorop_srgb_eotf, NULL }, "srgb_eotf" },
 		{ { &kms_colorop_srgb_inv_eotf, NULL }, "srgb_inv_eotf" },
@@ -409,15 +455,42 @@ int igt_main_args("d", long_options, help_str, opt_handler, NULL)
 		{ { &kms_colorop_3dlut_17_12_rgb, NULL }, "3dlut_17_12_rgb" },
 	};
 
+	/* YUV tests - CSC FF colorop with various encoding/range combinations and optional additional colorops */
+	struct {
+		kms_colorop_t *colorops[MAX_COLOROPS];
+		const char *name;
+	} tests_yuv[] = {
+		/* CSC only tests */
+		{ { &kms_colorop_csc_bt709_limited, NULL }, "csc_bt709_limited" },
+		{ { &kms_colorop_csc_bt709_full, NULL }, "csc_bt709_full" },
+		{ { &kms_colorop_csc_bt601_limited, NULL }, "csc_bt601_limited" },
+		{ { &kms_colorop_csc_bt2020_limited, NULL }, "csc_bt2020_limited" },
+		/* CSC + additional colorops */
+		{ { &kms_colorop_csc_bt709_limited, &kms_colorop_srgb_eotf, NULL }, "csc_bt709_limited-srgb_eotf" },
+		{ { &kms_colorop_csc_bt601_limited, &kms_colorop_srgb_eotf, NULL }, "csc_bt601_limited-srgb_eotf" },
+		{ { &kms_colorop_csc_bt709_limited, &kms_colorop_3dlut_17_12_rgb, NULL }, "csc_bt709_limited-3dlut_17_12_rgb" },
+		{ { &kms_colorop_csc_bt709_limited, &kms_colorop_ctm_3x4_50_desat, NULL }, "csc_bt709_limited-ctm_3x4_50_desat" },
+		{ { &kms_colorop_csc_bt709_limited, &kms_colorop_srgb_eotf, &kms_colorop_ctm_3x4_50_desat, NULL }, "csc_bt709_limited-srgb_eotf-ctm_3x4_50_desat" },
+	};
+
 	struct {
 		__u32 fourcc_in;
 		__u32 fourcc_out;
 		const char *name;
-	} formats[] = {
+	} formats_rgb[] = {
 		{ DRM_FORMAT_XRGB8888, DRM_FORMAT_XRGB8888, "XR24-XR24" },
 		{ DRM_FORMAT_XRGB2101010, DRM_FORMAT_XRGB2101010, "XR30-XR30" },
 	};
 
+	struct {
+		__u32 fourcc_in;
+		__u32 fourcc_out;
+		const char *name;
+	} formats_yuv[] = {
+		{ DRM_FORMAT_NV12, DRM_FORMAT_XRGB8888, "NV12-XR24" },
+		{ DRM_FORMAT_P010, DRM_FORMAT_XRGB2101010, "P010-XR30" },
+	};
+
 	igt_display_t display;
 	int i, j, ret;
 
@@ -448,7 +521,8 @@ int igt_main_args("d", long_options, help_str, opt_handler, NULL)
 		check_plane_colorop_ids(&display);
 	}
 
-	for (j = 0; j < ARRAY_SIZE(formats); j++) {
+	/* RGB format tests */
+	for (j = 0; j < ARRAY_SIZE(formats_rgb); j++) {
 		igt_output_t *output;
 		igt_plane_t *plane;
 		igt_fb_t input_fb, output_fb;
@@ -458,8 +532,8 @@ int igt_main_args("d", long_options, help_str, opt_handler, NULL)
 		igt_subtest_group() {
 			igt_fixture() {
 				output = kms_writeback_get_output(&display,
-								  formats[j].fourcc_in,
-								  formats[j].fourcc_out);
+								  formats_rgb[j].fourcc_in,
+								  formats_rgb[j].fourcc_out);
 				igt_require(output);
 
 				if (output->use_override_mode)
@@ -474,7 +548,7 @@ int igt_main_args("d", long_options, help_str, opt_handler, NULL)
 
 				fb_id = igt_create_color_pattern_fb(display.drm_fd,
 								mode.hdisplay, mode.vdisplay,
-								formats[j].fourcc_in, DRM_FORMAT_MOD_LINEAR,
+								formats_rgb[j].fourcc_in, DRM_FORMAT_MOD_LINEAR,
 								0.2, 0.2, 0.2, &input_fb);
 				igt_assert(fb_id >= 0);
 				igt_plane_set_fb(plane, &input_fb);
@@ -484,23 +558,24 @@ int igt_main_args("d", long_options, help_str, opt_handler, NULL)
 
 				/* create output fb */
 				fb_id = igt_create_fb(display.drm_fd, mode.hdisplay, mode.vdisplay,
-							formats[j].fourcc_in,
+							formats_rgb[j].fourcc_out,
 							igt_fb_mod_to_tiling(0),
 							&output_fb);
 				igt_require(fb_id > 0);
 			}
 
-			for (i = 0; i < ARRAY_SIZE(tests); i++) {
+			/* Run RGB tests */
+			for (i = 0; i < ARRAY_SIZE(tests_rgb); i++) {
 				igt_describe("Check color ops on a plane");
-				igt_subtest_f("plane-%s-%s", formats[j].name, tests[i].name)
+				igt_subtest_f("plane-%s-%s", formats_rgb[j].name, tests_rgb[i].name)
 					colorop_plane_test(&display,
 							output,
 							plane,
 							&input_fb,
 							&output_fb,
-							formats[j].fourcc_in,
-							formats[j].fourcc_out,
-							tests[i].colorops);
+							formats_rgb[j].fourcc_in,
+							formats_rgb[j].fourcc_out,
+							tests_rgb[i].colorops);
 			}
 
 			igt_fixture() {
@@ -512,6 +587,85 @@ int igt_main_args("d", long_options, help_str, opt_handler, NULL)
 		}
 	}
 
+	/* YUV format tests */
+	for (j = 0; j < ARRAY_SIZE(formats_yuv); j++) {
+		igt_output_t *output;
+		igt_plane_t *plane;
+		igt_fb_t output_fb;
+		unsigned int fb_id;
+		drmModeModeInfo mode;
+
+		igt_subtest_group() {
+			igt_fixture() {
+				output = kms_writeback_get_output(&display,
+								  formats_yuv[j].fourcc_in,
+								  formats_yuv[j].fourcc_out);
+				igt_require(output);
+
+				if (output->use_override_mode)
+					memcpy(&mode, &output->override_mode, sizeof(mode));
+				else
+					memcpy(&mode, &output->config.default_mode, sizeof(mode));
+
+				plane = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
+				igt_assert(plane);
+				igt_require(igt_plane_has_prop(plane, IGT_PLANE_COLOR_PIPELINE));
+
+				/* create output fb */
+				fb_id = igt_create_fb(display.drm_fd, mode.hdisplay, mode.vdisplay,
+							formats_yuv[j].fourcc_out,
+							igt_fb_mod_to_tiling(0),
+							&output_fb);
+				igt_require(fb_id > 0);
+			}
+
+			/* Run YUV tests - create input_fb per test with correct encoding/range */
+			for (i = 0; i < ARRAY_SIZE(tests_yuv); i++) {
+				igt_describe("Check YUV CSC colorop");
+				igt_subtest_f("plane-%s-%s", formats_yuv[j].name, tests_yuv[i].name) {
+					igt_fb_t input_fb;
+					enum igt_color_encoding encoding;
+					enum igt_color_range range;
+
+					/* Extract encoding and range from first colorop (must be CSC FF) */
+					igt_assert(tests_yuv[i].colorops[0]);
+					igt_assert(tests_yuv[i].colorops[0]->type == KMS_COLOROP_CSC_FF);
+					csc_ff_type_to_encoding_range(
+						tests_yuv[i].colorops[0]->csc_ff_info.csc_ff_type_name,
+						&encoding, &range);
+
+					/* Create input fb with matching encoding/range */
+					fb_id = igt_create_color_pattern_fb_yuv(display.drm_fd,
+									mode.hdisplay, mode.vdisplay,
+									formats_yuv[j].fourcc_in, DRM_FORMAT_MOD_LINEAR,
+									encoding, range,
+									0.2, 0.2, 0.2, &input_fb);
+					igt_assert(fb_id >= 0);
+
+					if (data.dump_check)
+						igt_dump_fb(&display, &input_fb, ".", "input");
+
+					colorop_plane_test(&display,
+							output,
+							plane,
+							&input_fb,
+							&output_fb,
+							formats_yuv[j].fourcc_in,
+							formats_yuv[j].fourcc_out,
+							tests_yuv[i].colorops);
+
+					igt_remove_fb(display.drm_fd, &input_fb);
+				}
+			}
+
+			igt_fixture() {
+				igt_detach_crtc(&display, output);
+				igt_remove_fb(display.drm_fd, &output_fb);
+
+			}
+		}
+	}
+
 	igt_fixture() {
 		igt_display_fini(&display);
 		drm_close_driver(display.drm_fd);
-- 
2.53.0


  parent reply	other threads:[~2026-03-30 15:35 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-30 15:35 [RFC PATCH v2 00/11] YUV Conversion Colorop tests Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 01/11] include/drm-uapi: Add DRM_COLOROP_CSC_FF and drm_colorop_csc_ff_type Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 02/11] lib/igt_kms, tests/kms_colorop_helper: Add CSC FF colorop infrastructure Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 03/11] tests/kms_colorop_helper: Add helpers to get encoding/range from CSC FF name Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 04/11] lib/igt_fb: Add YUV color pattern framebuffer support Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 05/11] lib/igt_color_encoding: Add XRGB2101010 format support Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 06/11] lib/igt_color: Add YUV pixel reading support Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 07/11] lib/igt_color: Refactor transform_pixels for input/output FBs and CSC Harry Wentland
2026-03-30 15:35 ` Harry Wentland [this message]
2026-03-30 15:35 ` [RFC PATCH v2 09/11] tests/kms_colorop: Keep CRTC active between YUV tests with temp FB Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 10/11] tests/kms_colorop: Add bypass transition tests Harry Wentland
2026-03-30 15:35 ` [RFC PATCH v2 11/11] tests/kms_colorop: Increase VKMS bracket to 3 up Harry Wentland
2026-03-31  3:38 ` ✓ Xe.CI.BAT: success for YUV Conversion Colorop tests Patchwork
2026-03-31  3:57 ` ✓ i915.CI.BAT: " Patchwork
2026-03-31 13:21 ` ✓ i915.CI.Full: " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260330153518.99898-9-harry.wentland@amd.com \
    --to=harry.wentland@amd.com \
    --cc=igt-dev@lists.freedesktop.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox