From 523785809d55f8f9e9f6658ab3e7788f31c398f1 Mon Sep 17 00:00:00 2001 From: "Leo (Sunpeng) Li" Date: Thu, 22 Mar 2018 14:27:13 -0400 Subject: Implemented demo app for degamma, ctm, and regamma. The app currently uses a system call to xrandr to set the blob IDs. Ideally, we should include libxrandr, and use the library to do so instead. Signed-off-by: Leo (Sunpeng) Li --- demo.c | 508 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 434 insertions(+), 74 deletions(-) diff --git a/demo.c b/demo.c index c501e3c..c612394 100644 --- a/demo.c +++ b/demo.c @@ -26,14 +26,15 @@ #include #include #include +#include #include #include + #include #include -#define LUT_SIZE 16 - +#define LUT_SIZE 4096 /** * The below data structures are identical to the ones used by DRM. They are @@ -66,6 +67,53 @@ struct color3d { * Helper functions */ +static int __fd_is_device(int fd, const char *expect) +{ + char name[5] = ""; + drm_version_t version = {0}; + + version.name_len = 4; + version.name = name; + if (drmIoctl(fd, DRM_IOCTL_VERSION, &version)) + return 0; + + return strcmp(expect, version.name) == 0; +} + +static int open_drm_device() +{ + char *base = "/dev/dri/card"; + int i = 0; + for (i = 0; i < 16; i++) { + char name[80]; + int fd; + + sprintf(name, "%s%u", base, i); + printf("Opening %s... ", name); + fd = open(name, O_RDWR); + if (fd == -1) { + printf("Failed.\n"); + continue; + } + + if (__fd_is_device(fd, "amdg")){ + printf("Success!\n"); + return fd; + } + + printf("Not an amdgpu device.\n"); + close(fd); + } + return -1; +} + +/** + * Translate coefficients to a color LUT format that DRM accepts. + * @coeffs: Input coefficients + * @lut: DRM LUT struct, used to create the blob. The translated values will be + * placed here. + * @lut_size: Number of entries in the LUT. + */ static void coeffs_to_lut(struct color3d *coeffs, struct _drm_color_lut *lut, int lut_size) @@ -80,6 +128,37 @@ static void coeffs_to_lut(struct color3d *coeffs, } } +/** + * Translate coefficients to a color CTM format that DRM accepts. + * + * DRM requres the CTM to be in signed-magnitude, not 2's complement. + * It is also in 31.32 fixed-point format. + * + * @coeffs: Input coefficients + * @ctm: DRM CTM struct, used to create the blob. The translated values will be + * placed here. + */ +static void coeffs_to_ctm(double *coeffs, + struct _drm_color_ctm *ctm) +{ + int i; + for (i = 0; i < 9; i++) { + if (coeffs[i] < 0) { + ctm->matrix[i] = + (int64_t) (-coeffs[i] * ((int64_t) 1L << 32)); + ctm->matrix[i] |= 1ULL << 63; + } else + ctm->matrix[i] = + (int64_t) (coeffs[i] * ((int64_t) 1L << 32)); + } +} + +/** + * The three functions below contain three different methods of generating a + * gamma LUT. They all output it in the intermediary coefficient format. + * + * Call coeffs_to_lut on the coefficients to translate them. + */ static void load_table_max(struct color3d *coeffs, int lut_size) { int i; @@ -95,121 +174,402 @@ static void load_table_zero(struct color3d *coeffs, int lut_size) coeffs[i].r = coeffs[i].g = coeffs[i].b = 0.0; } -static void load_table(struct color3d *coeffs, int lut_size, double exp) +static void load_table(struct color3d *coeffs, int lut_size, double *exps) { int i; + double sanitized_exps[3]; + + double min = 1.0 / (1 << 10); + + /* Ensure no zero exps. */ + for (i = 0; i < 3; i++) { + if (exps[i] < min) + sanitized_exps[i] = min; + else + sanitized_exps[i] = exps[i]; + } + for (i = 0; i < lut_size; i++) { - coeffs[i].r = coeffs[i].g = coeffs[i].b = - pow((double) i * 1.0 / (double) (lut_size - 1), exp); + coeffs[i].r = pow((double) i * 1.0 / (double) (lut_size - 1), + 1.0 / sanitized_exps[0]); + coeffs[i].g = pow((double) i * 1.0 / (double) (lut_size - 1), + 1.0 / sanitized_exps[1]); + coeffs[i].b = pow((double) i * 1.0 / (double) (lut_size - 1), + 1.0 / sanitized_exps[2]); } } -static void print_coeffs(const struct color3d *coeffs, int lut_size) +/** + * Create a DRM color LUT blob using the given coefficients, and set the + * output's CRTC to use it. Since setting degamma and regamma follows similar + * procedures, a flag is used to determine which one is set. Also note the + * special case of setting SRGB gamma, explained further below. + * + * @drm_fd: The file descriptor of the DRM interface. + * @coeffs: Coefficients used to create the DRM color LUT blob. + * @is_srgb: True if SRGB gamma is being programmed. This is a special case, + * since amdgpu DC defaults to SRGB when no DRM blob (i.e. NULL) is + * set. In other words, there is no need to create a blob (just set + * the blob id to 0) + * @is_degamma: True if degamma is being set. Set regamma otherwise. + */ +static int set_gamma(int drm_fd, struct color3d *coeffs, int is_srgb, + int is_degamma) { - int i; - for (i = 0; i < lut_size; i++) { - printf("[%d] R:%.2f G:%.2f B:%.2f\n", - i, coeffs[i].r, coeffs[i].g, coeffs[i].b); + struct _drm_color_lut lut[LUT_SIZE]; + uint32_t blob_id = 0; + + char randr_cmd[128]; + + int ret; + + if (!is_srgb) { + /* Using LUT */ + coeffs_to_lut(coeffs, lut, LUT_SIZE); + size_t size = sizeof(struct _drm_color_lut) * LUT_SIZE; + ret = drmModeCreatePropertyBlob(drm_fd, lut, size, &blob_id); + if (ret) { + printf("Failed to create blob. %d\n", ret); + return ret; + } + + printf("Created property blob with id %d\n", + blob_id); + } + /* Else: + * In the special case of SRGB, don't create the blob. We just need to + * set a NULL blob id (0) */ + + sprintf(randr_cmd, "xrandr --output DisplayPort-0 --set %s %d", + is_degamma ? "DEGAMMA_LUT" : "GAMMA_LUT", blob_id); + + printf("# %s\n", randr_cmd); + system(randr_cmd); + + if (blob_id) { + /* Make sure to destroy the blob if one was created. + * + * Note that we can destroy the blob immediately after it's set. + * The blob property is ref-counted within the kernel, and will + * be freed once the CRTC it's attached on is destroyed. + */ + ret = drmModeDestroyPropertyBlob(drm_fd, blob_id); + if (ret) { + printf("Failed to destroy blob. %d\n", ret); + return ret; + } + + printf("Destroyed property blob with id %d\n", blob_id); } + return 0; } -static void print_lut(const struct _drm_color_lut *lut, int lut_size) +/** + * Create a DRM color LUT blob using the given coefficients, and set the + * output's CRTC to use it. + * + * The process is similar to set_gamma(). The only difference is the type of + * blob being created. See set_gamma() for a description of the steps being + * done. + */ +static int set_ctm(int drm_fd, double *coeffs) { - int i; - for (i = 0; i < lut_size; i++) { - printf("[%d] R:%4x G:%4x B:%4x\n", - i, lut[i].red, lut[i].green, lut[i].blue); + struct _drm_color_ctm ctm; + uint32_t blob_id = 0; + size_t blob_size; + + char randr_cmd[128]; + + int ret; + + coeffs_to_ctm(coeffs, &ctm); + blob_size = sizeof(ctm); + ret = drmModeCreatePropertyBlob(drm_fd, &ctm, blob_size, &blob_id); + if (ret) { + printf("Failed to create blob. %d\n", ret); + return ret; + } + printf("Created property blob with id %d\n", blob_id); + + sprintf(randr_cmd, "xrandr --output DisplayPort-0 --set CTM %d", + blob_id); + + printf("# %s\n", randr_cmd); + system(randr_cmd); + + ret = drmModeDestroyPropertyBlob(drm_fd, blob_id); + if (ret) { + printf("Failed to destroy blob. %d\n", ret); + return ret; } + + printf("Destroyed property blob with id %d\n", blob_id); + + return 0; } -static int fd_is_device(int fd, const char *expect) +/******************************************************************************* + * main function, and functions to assist in parsing input. + */ + +/** + * Parse a list of doubles from the given string. ':' is used as the delimiter. + * + * @str: String containing doubles, delimited by ':' + * @count: Number of doubles expected to be found in the string. + * + * Return: an array of doubles. If the expected number of doubles is not met, + * return NULL. + */ +static double *parse_d(const char *str, int count) { - char name[5] = ""; - drm_version_t version = {0}; + char *token; + uint32_t len = strlen(str); + char *cpy_str; + char * const cpy_str_head = malloc(sizeof(char) * len); + double *ret = malloc(sizeof(double) * count); - version.name_len = 4; - version.name = name; - if (drmIoctl(fd, DRM_IOCTL_VERSION, &version)) - return 0; + cpy_str = cpy_str_head; + strcpy(cpy_str, str); - return strcmp(expect, version.name) == 0; + int i; + for (i = 0; i < count; i++) { + token = strsep(&cpy_str, ":"); + if (!token) + return NULL; + ret[i] = strtod(token, NULL); + } + + free(cpy_str_head); + return ret; } -static int open_drm_device() +/** + * Parse user input, and fill the coefficients array with the requested LUT. + * Degamma currently only supports srgb, or linear tables. + * + * @gamma_opt: User input + * @coeffs: Array of color3d structs. The requested LUT will be filled in here. + * @is_srgb: Will be set to true if user requested SRGB LUT. + * + * Return: True if user has requested gamma change. False otherwise. + */ +int parse_user_degamma(char *gamma_opt, struct color3d *coeffs, int *is_srgb) { - char *base = "/dev/dri/card"; - int i = 0; - for (i = 0; i < 16; i++) { - char name[80]; - int fd; + double linear_exps[3] = { 1.0, 1.0, 1.0 }; - sprintf(name, "%s%u", base, i); - printf("Opening %s... ", name); - fd = open(name, O_RDWR); - if (fd == -1) { - printf("Failed.\n"); - continue; - } + *is_srgb = 0; - if (fd_is_device(fd, "i915")){ - printf("Success!\n"); - return fd; - } + if (!gamma_opt) + return 0; - printf("Not an amdgpu device.\n"); - close(fd); + if (!strcmp(gamma_opt, "srgb")) { + printf("Using srgb degamma curve\n"); + *is_srgb = 1; + return 1; } - return -1; + if (!strcmp(gamma_opt, "linear")) { + printf("Using linear degamma curve\n"); + load_table(coeffs, LUT_SIZE, linear_exps); + return 1; + } + + printf("Degamma only supports 'srgb', or 'linear' LUT. Skipping.\n"); + return 0; } -/******************************************************************************* - * main +/** + * Parse user input, and fill the coefficients array with the requested CTM. + * + * @ctm_opt: user input + * @coeffs: Array of 9 doubles. The requested CTM will be filled in here. + * + * Return: True if user has requested CTM change. False otherwise. */ - -int main(int argc, char const *argv[]) +int parse_user_ctm(char *ctm_opt, double *coeffs) { - struct color3d coeffs[LUT_SIZE]; - struct _drm_color_lut lut[LUT_SIZE]; + if (!ctm_opt) + return 0; - int ret; + if (!strcmp(ctm_opt, "id")) { + printf("Using identity CTM\n"); + double temp[9] = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + }; + memcpy(coeffs, temp, sizeof(double) * 9); + return 1; + } + /* CTM is left-multiplied with the input color vector */ + if (!strcmp(ctm_opt, "rg")) { + printf("Using red-to-green CTM\n"); + double temp[9] = { + 0, 0, 0, + 1, 1, 0, + 0, 0, 1 + }; + memcpy(coeffs, temp, sizeof(double) * 9); + return 1; + } + if (!strcmp(ctm_opt, "rb")) { + printf("Using red-to-blue CTM\n"); + double temp[9] = { + 0, 0, 0, + 0, 1, 0, + 1, 0, 1 + }; + memcpy(coeffs, temp, sizeof(double) * 9); + return 1; + } - int fd = open_drm_device(); - if (fd == -1) { - printf("No valid devices found\n"); - return -1; + double *temp = parse_d(ctm_opt, 9); + if (!temp) { + printf("%s is not a valid CTM. Skipping.\n", + ctm_opt); + return 0; } - load_table(coeffs, LUT_SIZE, 1); - coeffs_to_lut(coeffs, lut, LUT_SIZE); + printf("Using custom CTM:\n"); + printf(" %2.4f:%2.4f:%2.4f\n", temp[0], temp[1], temp[2]); + printf(" %2.4f:%2.4f:%2.4f\n", temp[3], temp[4], temp[5]); + printf(" %2.4f:%2.4f:%2.4f\n", temp[6], temp[7], temp[8]); - size_t size = sizeof(struct _drm_color_lut) * LUT_SIZE; - uint32_t blob_id = 0; + memcpy(coeffs, temp, sizeof(double) * 9); + free(temp); + return 1; - ret = drmModeCreatePropertyBlob(fd, lut, size, &blob_id); - if (ret) { - printf("Failed to create blob. %d\n", ret); - return ret; +} + +/** + * Parse user input, and fill the coefficients array with the requested LUT. + * If predefined SRGB LUT is requested, the coefficients array is not touched, + * and is_srgb is set to true. See set_gamma() for why. + * + * @gamma_opt: User input + * @coeffs: Array of color3d structs. The requested LUT will be filled in here. + * @is_srgb: Will be set to true if user requested SRGB LUT. + * + * Return: True if user has requested gamma change. False otherwise. + */ +int parse_user_regamma(char *gamma_opt, struct color3d *coeffs, int *is_srgb) +{ + *is_srgb = 0; + + if (!gamma_opt) + return 0; + + if (!strcmp(gamma_opt, "max")) { + /* Use max gamma curve */ + printf("Using max regamma curve\n"); + load_table_max(coeffs, LUT_SIZE); + return 1; + } + if (!strcmp(gamma_opt, "min")) { + /* Use min gamma curve */ + printf("Using zero regamma curve\n"); + load_table_zero(coeffs, LUT_SIZE); + return 1; + } + if (!strcmp(gamma_opt, "srgb")) { + printf("Using srgb regamma curve\n"); + *is_srgb = 1; + return 1; } - printf("Successfully created property blob with id %d\n", blob_id); - drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(fd, blob_id); - if (!blob) { - printf("Failed to get blob.\n"); - return -1; + /* Custom exponential curve */ + double *exps = parse_d(gamma_opt, 3); + if (!exps) { + printf("%s is not a valid regamma exponent triple. Skipping.\n", + gamma_opt); + return 0; } + printf("Using custom regamma curve %.4f:%.4f:%.4f\n", + exps[0], exps[1], exps[2]); + load_table(coeffs, LUT_SIZE, exps); + free(exps); + return 1; +} - struct _drm_color_lut *ret_lut = (struct _drm_color_lut *)blob->data; +int main(int argc, char *const argv[]) +{ + struct color3d degamma_coeffs[LUT_SIZE]; + double ctm_coeffs[9]; + struct color3d regamma_coeffs[LUT_SIZE]; - print_lut(ret_lut, LUT_SIZE); + int drm_fd; + int ret; - ret = drmModeDestroyPropertyBlob(fd, blob_id); - if (ret) { - printf("Failed to destroy blob. %d\n", ret); + char cmd_enable_color_mgmt[] = + "xrandr --output DisplayPort-0 --set use_color_mgmt 1"; + + /* + * Parse arguments + */ + + int opt; + char *degamma_opt = NULL; + char *ctm_opt = NULL; + char *regamma_opt = NULL; + + int degamma_changed, degamma_is_srgb; + int ctm_changed; + int regamma_changed, regamma_is_srgb; + + while ((opt = getopt(argc, argv, "d:c:r:")) != -1) { + if (opt == 'd') + degamma_opt = optarg; + else if (opt == 'c') + ctm_opt = optarg; + else if (opt == 'r') + regamma_opt = optarg; + else { + printf("Invalid use.\n"); + return -1; + } + } + + /* Parse the input, and create an intermediate 'coefficient' form of + * the blob. Further translation is needed before the blob can be + * interpreted by DRM. + */ + degamma_changed = parse_user_degamma(degamma_opt, degamma_coeffs, + °amma_is_srgb); + ctm_changed = parse_user_ctm(ctm_opt, ctm_coeffs); + regamma_changed = parse_user_regamma(regamma_opt, regamma_coeffs, + ®amma_is_srgb); + + if (!degamma_changed && !ctm_changed && !regamma_changed) + return 0; + + drm_fd = open_drm_device(); + if (drm_fd == -1) { + printf("No valid devices found\n"); + printf("Did you run with admin privilege?\n"); return -1; } - drmModeFreePropertyBlob(blob); + /* Ensure non-legacy color management is enabled in xrandr */ + printf("# %s\n", cmd_enable_color_mgmt); + system(cmd_enable_color_mgmt); + + if (degamma_changed) { + ret = set_gamma(drm_fd, degamma_coeffs, degamma_is_srgb, 1); + if (ret) + return ret; + } + if (ctm_changed) { + ret = set_ctm(drm_fd, ctm_coeffs); + if (ret) + return ret; + } + if (regamma_changed) { + ret = set_gamma(drm_fd, regamma_coeffs, regamma_is_srgb, 0); + if (ret) + return ret; + } + close(drm_fd); printf("Done!\n"); return 0; -} \ No newline at end of file +} -- cgit v1.2.3